Compare commits
209 Commits
release-0.
...
fix_e2e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
478a1b9945 | ||
|
|
e328646617 | ||
|
|
6ebc914354 | ||
|
|
4be792ea54 | ||
|
|
50fbc2eec2 | ||
|
|
93f46e03ea | ||
|
|
59ed36e81f | ||
|
|
0820a9d32f | ||
|
|
7aeaa855e7 | ||
|
|
01bf238799 | ||
|
|
37a5aef6ea | ||
|
|
5424c5eb55 | ||
|
|
213688fd7d | ||
|
|
3eaacc01ae | ||
|
|
e20d13ace0 | ||
|
|
0ddeea3d78 | ||
|
|
bbc4fe30a6 | ||
|
|
7291a3bd71 | ||
|
|
826593d6ba | ||
|
|
6491d7b87f | ||
|
|
d04da92a23 | ||
|
|
fc741bf444 | ||
|
|
8afe1bea53 | ||
|
|
112772d02d | ||
|
|
a385f1ac82 | ||
|
|
1f19133ea8 | ||
|
|
7985ed5091 | ||
|
|
19c13b7401 | ||
|
|
3e6818d0b3 | ||
|
|
8cadff2b79 | ||
|
|
6862274e8e | ||
|
|
a02542b529 | ||
|
|
7dbbf52e1c | ||
|
|
9a9131d965 | ||
|
|
a6d50a8046 | ||
|
|
d47bb4f587 | ||
|
|
206b078c5f | ||
|
|
7c5f9ecc40 | ||
|
|
69fb81bcd3 | ||
|
|
c00cf69b55 | ||
|
|
0dfb744630 | ||
|
|
d95e590f5c | ||
|
|
d3710399f8 | ||
|
|
0eb9df178a | ||
|
|
e782d1be98 | ||
|
|
fb03520fb5 | ||
|
|
ed1e9ea400 | ||
|
|
df8d2cb68f | ||
|
|
38a5dd22e9 | ||
|
|
e598102f04 | ||
|
|
5de689ea1f | ||
|
|
887ea026bb | ||
|
|
75fb31a947 | ||
|
|
a1af9790ea | ||
|
|
96029a584f | ||
|
|
3bf7eacc7e | ||
|
|
6d6c62ae49 | ||
|
|
02d49ded39 | ||
|
|
3e7fe47131 | ||
|
|
038a6d7450 | ||
|
|
c4e3108549 | ||
|
|
6a696e03e7 | ||
|
|
797133f272 | ||
|
|
84da98c2b1 | ||
|
|
76047fe0af | ||
|
|
ee650342d5 | ||
|
|
1f8c736ba4 | ||
|
|
57a89b49ff | ||
|
|
6a5643287e | ||
|
|
e1a6ee9e2c | ||
|
|
ee480dece4 | ||
|
|
05e8ded744 | ||
|
|
ac65330c71 | ||
|
|
8a2c82267c | ||
|
|
fb70091169 | ||
|
|
f03a0bb247 | ||
|
|
bb3554a3c6 | ||
|
|
edb8f63848 | ||
|
|
bcb722b0b9 | ||
|
|
70b7eb52fa | ||
|
|
c59ac10e15 | ||
|
|
584a8bf13d | ||
|
|
b88ca7f8cd | ||
|
|
8f7894e598 | ||
|
|
3de4bf527b | ||
|
|
f90288133d | ||
|
|
70d2751030 | ||
|
|
9b14c227a9 | ||
|
|
e2745b453f | ||
|
|
a6eef5a8cf | ||
|
|
3174467751 | ||
|
|
df8d1aba5c | ||
|
|
c099a70c20 | ||
|
|
79e96bbe37 | ||
|
|
b9823943e3 | ||
|
|
c8ed21cac4 | ||
|
|
6b93cc2ad9 | ||
|
|
086b2e1ddd | ||
|
|
2b4487ba9a | ||
|
|
cad15d9961 | ||
|
|
9ec155b843 | ||
|
|
e886f5d24e | ||
|
|
acc3696057 | ||
|
|
288bb824aa | ||
|
|
6fe0beabcd | ||
|
|
0fbd33788e | ||
|
|
1b5ad035d9 | ||
|
|
ee5300db4c | ||
|
|
6309529a3f | ||
|
|
2c74a560c4 | ||
|
|
daecc2a0bc | ||
|
|
7c8905f10d | ||
|
|
3a7e0908bd | ||
|
|
d1f7c32760 | ||
|
|
8306d92c79 | ||
|
|
abecadf707 | ||
|
|
e9d1ba88a8 | ||
|
|
ad62f90e54 | ||
|
|
6de6b37406 | ||
|
|
7756b5ce04 | ||
|
|
19b0797ae2 | ||
|
|
8c7e58a231 | ||
|
|
6b5001bf0e | ||
|
|
e12b5029d7 | ||
|
|
86eea326db | ||
|
|
f251ddda98 | ||
|
|
f81d19e692 | ||
|
|
c728870b49 | ||
|
|
1e1f8819bf | ||
|
|
0733c83a0a | ||
|
|
c9e4786893 | ||
|
|
8c4cb7238c | ||
|
|
821180bdf1 | ||
|
|
d2fa4cc0b8 | ||
|
|
046e018c80 | ||
|
|
9f23e39fca | ||
|
|
088578b055 | ||
|
|
0d1d4fa052 | ||
|
|
ac0574a377 | ||
|
|
35ce0c5049 | ||
|
|
489f322514 | ||
|
|
f3eac80675 | ||
|
|
f21fd951ef | ||
|
|
99b3b40342 | ||
|
|
24fcef14ef | ||
|
|
f7d4658cf1 | ||
|
|
6ab338cf58 | ||
|
|
9a75468a32 | ||
|
|
6193210d85 | ||
|
|
941eabb605 | ||
|
|
311414e63a | ||
|
|
3ca08c4f12 | ||
|
|
ab19e7258f | ||
|
|
0255214d97 | ||
|
|
31ffaa0e71 | ||
|
|
61b52ce4ae | ||
|
|
d10b40acb0 | ||
|
|
6542c2ee94 | ||
|
|
9f088b87ee | ||
|
|
e513e6ca59 | ||
|
|
6261f507a3 | ||
|
|
0ab16e11b8 | ||
|
|
36643b77b4 | ||
|
|
e272d725a5 | ||
|
|
a8f4143f53 | ||
|
|
28d93fba90 | ||
|
|
1ab8523d8a | ||
|
|
5614d9158b | ||
|
|
305dc6ce91 | ||
|
|
cc7e94b07c | ||
|
|
259959c0a5 | ||
|
|
3422e8a40c | ||
|
|
d806fb9126 | ||
|
|
a25ab90e05 | ||
|
|
845df22a32 | ||
|
|
9f37a93859 | ||
|
|
3b898042cd | ||
|
|
01ce79c453 | ||
|
|
51f1ae94ef | ||
|
|
5d33c94d04 | ||
|
|
a3bf13711c | ||
|
|
f2c37b9de6 | ||
|
|
b59bc960f2 | ||
|
|
0263c985cf | ||
|
|
3f0404d9e3 | ||
|
|
58721e0c20 | ||
|
|
743fbb1da4 | ||
|
|
24dd4b54bf | ||
|
|
60f0ccd8a2 | ||
|
|
2ba1376400 | ||
|
|
e51a8c92cf | ||
|
|
6301503095 | ||
|
|
1d26921710 | ||
|
|
e232af1073 | ||
|
|
a504fe7195 | ||
|
|
4dc407f600 | ||
|
|
bd7c4e04d4 | ||
|
|
4528e0c374 | ||
|
|
d233d93cbf | ||
|
|
8fce69d373 | ||
|
|
e843262064 | ||
|
|
298a772d68 | ||
|
|
64ea86436f | ||
|
|
19abddf1fe | ||
|
|
166094b5ad | ||
|
|
2ac000c68a | ||
|
|
81f592de74 | ||
|
|
863628ffaa | ||
|
|
be1acb72ac |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
**
|
||||||
|
|
||||||
|
!/bin/linux
|
||||||
64
.github/workflows/ci.yml
vendored
64
.github/workflows/ci.yml
vendored
@@ -3,14 +3,28 @@ name: CI
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * *'
|
- cron: '0 0 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
|
vendor:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.18
|
||||||
|
- name: Vendor
|
||||||
|
run: |
|
||||||
|
make vendor
|
||||||
|
git diff --exit-code
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -18,10 +32,23 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15.7
|
go-version: 1.18
|
||||||
- name: Build
|
- name: Build
|
||||||
run: make
|
run: make
|
||||||
|
|
||||||
|
docs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.18
|
||||||
|
- name: Build docs
|
||||||
|
run: |
|
||||||
|
make gen-docs
|
||||||
|
git diff --exit-code
|
||||||
|
|
||||||
linux:
|
linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -29,7 +56,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15.7
|
go-version: 1.18
|
||||||
- name: Build kg and kgctl for all Linux Architectures
|
- name: Build kg and kgctl for all Linux Architectures
|
||||||
run: make all-build
|
run: make all-build
|
||||||
|
|
||||||
@@ -40,9 +67,11 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15.7
|
go-version: 1.18
|
||||||
- name: Build kgctl for Darwin
|
- name: Build kgctl for Darwin amd64
|
||||||
run: make OS=darwin
|
run: make OS=darwin ARCH=amd64
|
||||||
|
- name: Build kgctl for Darwin arm64
|
||||||
|
run: make OS=darwin ARCH=arm64
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -51,7 +80,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15.7
|
go-version: 1.18
|
||||||
- name: Build kgctl for Windows
|
- name: Build kgctl for Windows
|
||||||
run: make OS=windows
|
run: make OS=windows
|
||||||
|
|
||||||
@@ -62,10 +91,22 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15.7
|
go-version: 1.18
|
||||||
- name: Run Unit Tests
|
- name: Run Unit Tests
|
||||||
run: make unit
|
run: make unit
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.18
|
||||||
|
- name: Run e2e Tests
|
||||||
|
run: make e2e
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -73,7 +114,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15.7
|
go-version: 1.18
|
||||||
- name: Lint Code
|
- name: Lint Code
|
||||||
run: make lint
|
run: make lint
|
||||||
|
|
||||||
@@ -84,7 +125,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15.7
|
go-version: 1.18
|
||||||
- name: Enable Experimental Docker CLI
|
- name: Enable Experimental Docker CLI
|
||||||
run: |
|
run: |
|
||||||
echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
|
echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
|
||||||
@@ -100,6 +141,7 @@ jobs:
|
|||||||
push:
|
push:
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
needs:
|
needs:
|
||||||
|
- vendor
|
||||||
- build
|
- build
|
||||||
- linux
|
- linux
|
||||||
- darwin
|
- darwin
|
||||||
@@ -113,7 +155,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15.7
|
go-version: 1.18
|
||||||
- name: Enable Experimental Docker CLI
|
- name: Enable Experimental Docker CLI
|
||||||
run: |
|
run: |
|
||||||
echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
|
echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
|
||||||
|
|||||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -3,15 +3,15 @@ on:
|
|||||||
types: [created]
|
types: [created]
|
||||||
name: Handle Release
|
name: Handle Release
|
||||||
jobs:
|
jobs:
|
||||||
linux:
|
kgctl:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15.7
|
go-version: 1.18
|
||||||
- name: Make Directory with kgctl Binaries to Be Released
|
- name: Build kgctl Binaries to Be Released
|
||||||
run: make release
|
run: make release
|
||||||
- name: Publish Release
|
- name: Publish Release
|
||||||
uses: skx/github-action-publish-binaries@master
|
uses: skx/github-action-publish-binaries@master
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@
|
|||||||
.push*
|
.push*
|
||||||
bin/
|
bin/
|
||||||
tmp/
|
tmp/
|
||||||
|
e2e/kind.yaml*
|
||||||
|
|||||||
14
Dockerfile
14
Dockerfile
@@ -1,15 +1,19 @@
|
|||||||
ARG FROM=alpine
|
ARG FROM=alpine
|
||||||
FROM alpine AS cni
|
FROM $FROM AS cni
|
||||||
ARG GOARCH
|
ARG GOARCH=amd64
|
||||||
|
ARG CNI_PLUGINS_VERSION=v1.1.1
|
||||||
RUN apk add --no-cache curl && \
|
RUN apk add --no-cache curl && \
|
||||||
curl -Lo cni.tar.gz https://github.com/containernetworking/plugins/releases/download/v0.7.5/cni-plugins-$GOARCH-v0.7.5.tgz && \
|
curl -Lo cni.tar.gz https://github.com/containernetworking/plugins/releases/download/$CNI_PLUGINS_VERSION/cni-plugins-linux-$GOARCH-$CNI_PLUGINS_VERSION.tgz && \
|
||||||
tar -xf cni.tar.gz
|
tar -xf cni.tar.gz
|
||||||
|
|
||||||
FROM $FROM
|
FROM $FROM
|
||||||
ARG GOARCH
|
ARG GOARCH
|
||||||
|
ARG ALPINE_VERSION=v3.12
|
||||||
LABEL maintainer="squat <lserven@gmail.com>"
|
LABEL maintainer="squat <lserven@gmail.com>"
|
||||||
RUN echo -e "https://alpine.global.ssl.fastly.net/alpine/v3.12/main\nhttps://alpine.global.ssl.fastly.net/alpine/v3.12/community" > /etc/apk/repositories && \
|
RUN echo -e "https://alpine.global.ssl.fastly.net/alpine/$ALPINE_VERSION/main\nhttps://alpine.global.ssl.fastly.net/alpine/$ALPINE_VERSION/community" > /etc/apk/repositories && \
|
||||||
apk add --no-cache ipset iptables ip6tables wireguard-tools
|
apk add --no-cache ipset iptables ip6tables graphviz font-noto
|
||||||
COPY --from=cni bridge host-local loopback portmap /opt/cni/bin/
|
COPY --from=cni bridge host-local loopback portmap /opt/cni/bin/
|
||||||
|
ADD https://raw.githubusercontent.com/kubernetes-sigs/iptables-wrappers/e139a115350974aac8a82ec4b815d2845f86997e/iptables-wrapper-installer.sh /
|
||||||
|
RUN chmod 700 /iptables-wrapper-installer.sh && /iptables-wrapper-installer.sh --no-sanity-check
|
||||||
COPY bin/linux/$GOARCH/kg /opt/bin/
|
COPY bin/linux/$GOARCH/kg /opt/bin/
|
||||||
ENTRYPOINT ["/opt/bin/kg"]
|
ENTRYPOINT ["/opt/bin/kg"]
|
||||||
|
|||||||
105
Makefile
105
Makefile
@@ -1,9 +1,8 @@
|
|||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
.PHONY: push container clean container-name container-latest push-latest fmt lint test unit vendor header generate client deepcopy informer lister openapi manifest manfest-latest manifest-annotate manifest manfest-latest manifest-annotate release
|
.PHONY: push container clean container-name container-latest push-latest fmt lint test unit vendor header generate crd client deepcopy informer lister manifest manfest-latest manifest-annotate manifest manfest-latest manifest-annotate release gen-docs e2e
|
||||||
|
|
||||||
OS ?= $(shell go env GOOS)
|
OS ?= $(shell go env GOOS)
|
||||||
ARCH ?= $(shell go env GOARCH)
|
ARCH ?= $(shell go env GOARCH)
|
||||||
ALL_OS := linux darwin windows
|
|
||||||
ALL_ARCH := amd64 arm arm64
|
ALL_ARCH := amd64 arm arm64
|
||||||
DOCKER_ARCH := "amd64" "arm v7" "arm64 v8"
|
DOCKER_ARCH := "amd64" "arm v7" "arm64 v8"
|
||||||
ifeq ($(OS),linux)
|
ifeq ($(OS),linux)
|
||||||
@@ -11,12 +10,12 @@ ifeq ($(OS),linux)
|
|||||||
else
|
else
|
||||||
BINS := bin/$(OS)/$(ARCH)/kgctl
|
BINS := bin/$(OS)/$(ARCH)/kgctl
|
||||||
endif
|
endif
|
||||||
RELEASE_BINS := $(addprefix bin/release/kgctl-, $(addprefix linux-, $(ALL_ARCH)) darwin-amd64 windows-amd64)
|
RELEASE_BINS := $(addprefix bin/release/kgctl-, $(addprefix linux-, $(ALL_ARCH)) darwin-amd64 darwin-arm64 windows-amd64)
|
||||||
CLIENT_BINS := $(addsuffix /kgctl, $(addprefix bin/, $(addprefix linux/, $(ALL_ARCH)) darwin/amd64 windows/amd64))
|
|
||||||
PROJECT := kilo
|
PROJECT := kilo
|
||||||
PKG := github.com/squat/$(PROJECT)
|
PKG := github.com/squat/$(PROJECT)
|
||||||
REGISTRY ?= index.docker.io
|
REGISTRY ?= index.docker.io
|
||||||
IMAGE ?= squat/$(PROJECT)
|
IMAGE ?= squat/$(PROJECT)
|
||||||
|
FULLY_QUALIFIED_IMAGE := $(REGISTRY)/$(IMAGE)
|
||||||
|
|
||||||
TAG := $(shell git describe --abbrev=0 --tags HEAD 2>/dev/null)
|
TAG := $(shell git describe --abbrev=0 --tags HEAD 2>/dev/null)
|
||||||
COMMIT := $(shell git rev-parse HEAD)
|
COMMIT := $(shell git rev-parse HEAD)
|
||||||
@@ -33,16 +32,21 @@ SRC := $(shell find . -type f -name '*.go' -not -path "./vendor/*")
|
|||||||
GO_FILES ?= $$(find . -name '*.go' -not -path './vendor/*')
|
GO_FILES ?= $$(find . -name '*.go' -not -path './vendor/*')
|
||||||
GO_PKGS ?= $$(go list ./... | grep -v "$(PKG)/vendor")
|
GO_PKGS ?= $$(go list ./... | grep -v "$(PKG)/vendor")
|
||||||
|
|
||||||
|
CONTROLLER_GEN_BINARY := bin/controller-gen
|
||||||
CLIENT_GEN_BINARY := bin/client-gen
|
CLIENT_GEN_BINARY := bin/client-gen
|
||||||
|
DOCS_GEN_BINARY := bin/docs-gen
|
||||||
DEEPCOPY_GEN_BINARY := bin/deepcopy-gen
|
DEEPCOPY_GEN_BINARY := bin/deepcopy-gen
|
||||||
INFORMER_GEN_BINARY := bin/informer-gen
|
INFORMER_GEN_BINARY := bin/informer-gen
|
||||||
LISTER_GEN_BINARY := bin/lister-gen
|
LISTER_GEN_BINARY := bin/lister-gen
|
||||||
OPENAPI_GEN_BINARY := bin/openapi-gen
|
STATICCHECK_BINARY := bin/staticcheck
|
||||||
GOLINT_BINARY := bin/golint
|
|
||||||
EMBEDMD_BINARY := bin/embedmd
|
EMBEDMD_BINARY := bin/embedmd
|
||||||
|
KIND_BINARY := $(shell pwd)/bin/kind
|
||||||
|
KUBECTL_BINARY := $(shell pwd)/bin/kubectl
|
||||||
|
BASH_UNIT := $(shell pwd)/bin/bash_unit
|
||||||
|
BASH_UNIT_FLAGS :=
|
||||||
|
|
||||||
BUILD_IMAGE ?= golang:1.15.7-alpine
|
BUILD_IMAGE ?= golang:1.18.0
|
||||||
BASE_IMAGE ?= alpine:3.12
|
BASE_IMAGE ?= alpine:3.15
|
||||||
|
|
||||||
build: $(BINS)
|
build: $(BINS)
|
||||||
|
|
||||||
@@ -71,7 +75,13 @@ all-container-latest: $(addprefix container-latest-, $(ALL_ARCH))
|
|||||||
|
|
||||||
all-push-latest: $(addprefix push-latest-, $(ALL_ARCH))
|
all-push-latest: $(addprefix push-latest-, $(ALL_ARCH))
|
||||||
|
|
||||||
generate: client deepcopy informer lister openapi
|
generate: client deepcopy informer lister crd
|
||||||
|
|
||||||
|
crd: manifests/crds.yaml
|
||||||
|
manifests/crds.yaml: pkg/k8s/apis/kilo/v1alpha1/types.go $(CONTROLLER_GEN_BINARY)
|
||||||
|
$(CONTROLLER_GEN_BINARY) crd \
|
||||||
|
paths=./pkg/k8s/apis/kilo/... \
|
||||||
|
output:crd:stdout > $@
|
||||||
|
|
||||||
client: pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go
|
client: pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go
|
||||||
pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go $(CLIENT_GEN_BINARY)
|
pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go $(CLIENT_GEN_BINARY)
|
||||||
@@ -129,16 +139,9 @@ pkg/k8s/listers/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.
|
|||||||
rm -r github.com || true
|
rm -r github.com || true
|
||||||
go fmt ./pkg/k8s/listers/...
|
go fmt ./pkg/k8s/listers/...
|
||||||
|
|
||||||
openapi: pkg/k8s/apis/kilo/v1alpha1/openapi_generated.go
|
gen-docs: generate docs/api.md docs/kg.md
|
||||||
pkg/k8s/apis/kilo/v1alpha1/openapi_generated.go: pkg/k8s/apis/kilo/v1alpha1/types.go $(OPENAPI_GEN_BINARY)
|
docs/api.md: pkg/k8s/apis/kilo/v1alpha1/types.go $(DOCS_GEN_BINARY)
|
||||||
$(OPENAPI_GEN_BINARY) \
|
$(DOCS_GEN_BINARY) $< > $@
|
||||||
--input-dirs $(PKG)/$(@D),k8s.io/apimachinery/pkg/apis/meta/v1,k8s.io/api/core/v1 \
|
|
||||||
--output-base $(CURDIR) \
|
|
||||||
--output-package ./$(@D) \
|
|
||||||
--logtostderr \
|
|
||||||
--report-filename /dev/null \
|
|
||||||
--go-header-file=.header
|
|
||||||
go fmt $@
|
|
||||||
|
|
||||||
$(BINS): $(SRC) go.mod
|
$(BINS): $(SRC) go.mod
|
||||||
@mkdir -p bin/$(word 2,$(subst /, ,$@))/$(word 3,$(subst /, ,$@))
|
@mkdir -p bin/$(word 2,$(subst /, ,$@))/$(word 3,$(subst /, ,$@))
|
||||||
@@ -162,7 +165,7 @@ fmt:
|
|||||||
@echo $(GO_PKGS)
|
@echo $(GO_PKGS)
|
||||||
gofmt -w -s $(GO_FILES)
|
gofmt -w -s $(GO_FILES)
|
||||||
|
|
||||||
lint: header $(GOLINT_BINARY)
|
lint: header $(STATICCHECK_BINARY)
|
||||||
@echo 'go vet $(GO_PKGS)'
|
@echo 'go vet $(GO_PKGS)'
|
||||||
@vet_res=$$(GO111MODULE=on go vet -mod=vendor $(GO_PKGS) 2>&1); if [ -n "$$vet_res" ]; then \
|
@vet_res=$$(GO111MODULE=on go vet -mod=vendor $(GO_PKGS) 2>&1); if [ -n "$$vet_res" ]; then \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
@@ -171,10 +174,10 @@ lint: header $(GOLINT_BINARY)
|
|||||||
echo "$$vet_res"; \
|
echo "$$vet_res"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
@echo '$(GOLINT_BINARY) $(GO_PKGS)'
|
@echo '$(STATICCHECK_BINARY) $(GO_PKGS)'
|
||||||
@lint_res=$$($(GOLINT_BINARY) $(GO_PKGS)); if [ -n "$$lint_res" ]; then \
|
@lint_res=$$($(STATICCHECK_BINARY) $(GO_PKGS)); if [ -n "$$lint_res" ]; then \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
echo "Golint found style issues. Please check the reported issues"; \
|
echo "Staticcheck found style issues. Please check the reported issues"; \
|
||||||
echo "and fix them if necessary before submitting the code for review:"; \
|
echo "and fix them if necessary before submitting the code for review:"; \
|
||||||
echo "$$lint_res"; \
|
echo "$$lint_res"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
@@ -191,7 +194,22 @@ lint: header $(GOLINT_BINARY)
|
|||||||
unit:
|
unit:
|
||||||
go test -mod=vendor --race ./...
|
go test -mod=vendor --race ./...
|
||||||
|
|
||||||
test: lint unit
|
test: lint unit e2e
|
||||||
|
|
||||||
|
$(KIND_BINARY):
|
||||||
|
curl -Lo $@ https://kind.sigs.k8s.io/dl/v0.11.1/kind-linux-$(ARCH)
|
||||||
|
chmod +x $@
|
||||||
|
|
||||||
|
$(KUBECTL_BINARY):
|
||||||
|
curl -Lo $@ https://dl.k8s.io/release/v1.21.0/bin/linux/$(ARCH)/kubectl
|
||||||
|
chmod +x $@
|
||||||
|
|
||||||
|
$(BASH_UNIT):
|
||||||
|
curl -Lo $@ https://raw.githubusercontent.com/pgrange/bash_unit/v1.7.2/bash_unit
|
||||||
|
chmod +x $@
|
||||||
|
|
||||||
|
e2e: container $(KIND_BINARY) $(KUBECTL_BINARY) $(BASH_UNIT) bin/$(OS)/$(ARCH)/kgctl
|
||||||
|
KILO_IMAGE=$(IMAGE):$(ARCH)-$(VERSION) KIND_BINARY=$(KIND_BINARY) KUBECTL_BINARY=$(KUBECTL_BINARY) KGCTL_BINARY=$(shell pwd)/bin/$(OS)/$(ARCH)/kgctl $(BASH_UNIT) $(BASH_UNIT_FLAGS) ./e2e/setup.sh ./e2e/full-mesh.sh ./e2e/location-mesh.sh ./e2e/multi-cluster.sh ./e2e/handlers.sh ./e2e/kgctl.sh ./e2e/teardown.sh
|
||||||
|
|
||||||
header: .header
|
header: .header
|
||||||
@HEADER=$$(cat .header); \
|
@HEADER=$$(cat .header); \
|
||||||
@@ -224,10 +242,12 @@ website/docs/README.md: README.md
|
|||||||
cat README.md >> $@
|
cat README.md >> $@
|
||||||
cp -r docs/graphs website/static/img/
|
cp -r docs/graphs website/static/img/
|
||||||
sed -i 's/\.\/docs\///g' $@
|
sed -i 's/\.\/docs\///g' $@
|
||||||
find $(@D) -type f -name '*.md' | xargs -I{} sed -i 's/\.\/\(.\+\.svg\)/\/img\/\1/g' {}
|
find $(@D) -type f -name '*.md' | xargs -I{} sed -i 's/\.\/\(.\+\.\(svg\|png\)\)/\/img\/\1/g' {}
|
||||||
sed -i 's/graphs\//\/img\/graphs\//g' $@
|
sed -i 's/graphs\//\/img\/graphs\//g' $@
|
||||||
|
# The next line is a workaround until mdx, docusaurus' markdown parser, can parse links with preceding brackets.
|
||||||
|
sed -i 's/\[\]\(\[.*\](.*)\)/\[\]\1/g' website/docs/api.md
|
||||||
|
|
||||||
website/build/index.html: website/docs/README.md
|
website/build/index.html: website/docs/README.md docs/api.md
|
||||||
yarn --cwd website install
|
yarn --cwd website install
|
||||||
yarn --cwd website build
|
yarn --cwd website build
|
||||||
|
|
||||||
@@ -243,7 +263,7 @@ container: .container-$(ARCH)-$(VERSION) container-name
|
|||||||
@docker images -q $(IMAGE):$(ARCH)-$(VERSION) > $@
|
@docker images -q $(IMAGE):$(ARCH)-$(VERSION) > $@
|
||||||
|
|
||||||
container-latest: .container-$(ARCH)-$(VERSION)
|
container-latest: .container-$(ARCH)-$(VERSION)
|
||||||
@docker tag $(IMAGE):$(ARCH)-$(VERSION) $(IMAGE):$(ARCH)-latest
|
@docker tag $(IMAGE):$(ARCH)-$(VERSION) $(FULLY_QUALIFIED_IMAGE):$(ARCH)-latest
|
||||||
@echo "container: $(IMAGE):$(ARCH)-latest"
|
@echo "container: $(IMAGE):$(ARCH)-latest"
|
||||||
|
|
||||||
container-name:
|
container-name:
|
||||||
@@ -251,14 +271,15 @@ container-name:
|
|||||||
|
|
||||||
manifest: .manifest-$(VERSION) manifest-name
|
manifest: .manifest-$(VERSION) manifest-name
|
||||||
.manifest-$(VERSION): Dockerfile $(addprefix push-, $(ALL_ARCH))
|
.manifest-$(VERSION): Dockerfile $(addprefix push-, $(ALL_ARCH))
|
||||||
@docker manifest create --amend $(IMAGE):$(VERSION) $(addsuffix -$(VERSION), $(addprefix squat/$(PROJECT):, $(ALL_ARCH)))
|
@docker manifest create --amend $(FULLY_QUALIFIED_IMAGE):$(VERSION) $(addsuffix -$(VERSION), $(addprefix $(FULLY_QUALIFIED_IMAGE):, $(ALL_ARCH)))
|
||||||
@$(MAKE) --no-print-directory manifest-annotate-$(VERSION)
|
@$(MAKE) --no-print-directory manifest-annotate-$(VERSION)
|
||||||
@docker manifest push $(IMAGE):$(VERSION) > $@
|
@docker manifest push $(FULLY_QUALIFIED_IMAGE):$(VERSION) > $@
|
||||||
|
|
||||||
manifest-latest: Dockerfile $(addprefix push-latest-, $(ALL_ARCH))
|
manifest-latest: Dockerfile $(addprefix push-latest-, $(ALL_ARCH))
|
||||||
@docker manifest create --amend $(IMAGE):latest $(addsuffix -latest, $(addprefix squat/$(PROJECT):, $(ALL_ARCH)))
|
@docker manifest rm $(FULLY_QUALIFIED_IMAGE):latest || echo no old manifest
|
||||||
|
@docker manifest create --amend $(FULLY_QUALIFIED_IMAGE):latest $(addsuffix -latest, $(addprefix $(FULLY_QUALIFIED_IMAGE):, $(ALL_ARCH)))
|
||||||
@$(MAKE) --no-print-directory manifest-annotate-latest
|
@$(MAKE) --no-print-directory manifest-annotate-latest
|
||||||
@docker manifest push $(IMAGE):latest
|
@docker manifest push $(FULLY_QUALIFIED_IMAGE):latest
|
||||||
@echo "manifest: $(IMAGE):latest"
|
@echo "manifest: $(IMAGE):latest"
|
||||||
|
|
||||||
manifest-annotate: manifest-annotate-$(VERSION)
|
manifest-annotate: manifest-annotate-$(VERSION)
|
||||||
@@ -269,7 +290,7 @@ manifest-annotate-%:
|
|||||||
annotate=; \
|
annotate=; \
|
||||||
j=0; for da in $(DOCKER_ARCH); do \
|
j=0; for da in $(DOCKER_ARCH); do \
|
||||||
if [ "$$j" -eq "$$i" ] && [ -n "$$da" ]; then \
|
if [ "$$j" -eq "$$i" ] && [ -n "$$da" ]; then \
|
||||||
annotate="docker manifest annotate $(IMAGE):$* $(IMAGE):$$a-$* --os linux --arch"; \
|
annotate="docker manifest annotate $(FULLY_QUALIFIED_IMAGE):$* $(FULLY_QUALIFIED_IMAGE):$$a-$* --os linux --arch"; \
|
||||||
k=0; for ea in $$da; do \
|
k=0; for ea in $$da; do \
|
||||||
[ "$$k" = 0 ] && annotate="$$annotate $$ea"; \
|
[ "$$k" = 0 ] && annotate="$$annotate $$ea"; \
|
||||||
[ "$$k" != 0 ] && annotate="$$annotate --variant $$ea"; \
|
[ "$$k" != 0 ] && annotate="$$annotate --variant $$ea"; \
|
||||||
@@ -283,15 +304,18 @@ manifest-annotate-%:
|
|||||||
done
|
done
|
||||||
|
|
||||||
manifest-name:
|
manifest-name:
|
||||||
@echo "manifest: $(IMAGE_ROOT):$(VERSION)"
|
@echo "manifest: $(IMAGE):$(VERSION)"
|
||||||
|
|
||||||
push: .push-$(ARCH)-$(VERSION) push-name
|
push: .push-$(ARCH)-$(VERSION) push-name
|
||||||
.push-$(ARCH)-$(VERSION): .container-$(ARCH)-$(VERSION)
|
.push-$(ARCH)-$(VERSION): .container-$(ARCH)-$(VERSION)
|
||||||
@docker push $(REGISTRY)/$(IMAGE):$(ARCH)-$(VERSION)
|
ifneq ($(REGISTRY),index.docker.io)
|
||||||
|
@docker tag $(IMAGE):$(ARCH)-$(VERSION) $(FULLY_QUALIFIED_IMAGE):$(ARCH)-$(VERSION)
|
||||||
|
endif
|
||||||
|
@docker push $(FULLY_QUALIFIED_IMAGE):$(ARCH)-$(VERSION)
|
||||||
@docker images -q $(IMAGE):$(ARCH)-$(VERSION) > $@
|
@docker images -q $(IMAGE):$(ARCH)-$(VERSION) > $@
|
||||||
|
|
||||||
push-latest: container-latest
|
push-latest: container-latest
|
||||||
@docker push $(REGISTRY)/$(IMAGE):$(ARCH)-latest
|
@docker push $(FULLY_QUALIFIED_IMAGE):$(ARCH)-latest
|
||||||
@echo "pushed: $(IMAGE):$(ARCH)-latest"
|
@echo "pushed: $(IMAGE):$(ARCH)-latest"
|
||||||
|
|
||||||
push-name:
|
push-name:
|
||||||
@@ -316,6 +340,9 @@ vendor:
|
|||||||
go mod tidy
|
go mod tidy
|
||||||
go mod vendor
|
go mod vendor
|
||||||
|
|
||||||
|
$(CONTROLLER_GEN_BINARY):
|
||||||
|
go build -mod=vendor -o $@ sigs.k8s.io/controller-tools/cmd/controller-gen
|
||||||
|
|
||||||
$(CLIENT_GEN_BINARY):
|
$(CLIENT_GEN_BINARY):
|
||||||
go build -mod=vendor -o $@ k8s.io/code-generator/cmd/client-gen
|
go build -mod=vendor -o $@ k8s.io/code-generator/cmd/client-gen
|
||||||
|
|
||||||
@@ -328,11 +355,11 @@ $(INFORMER_GEN_BINARY):
|
|||||||
$(LISTER_GEN_BINARY):
|
$(LISTER_GEN_BINARY):
|
||||||
go build -mod=vendor -o $@ k8s.io/code-generator/cmd/lister-gen
|
go build -mod=vendor -o $@ k8s.io/code-generator/cmd/lister-gen
|
||||||
|
|
||||||
$(OPENAPI_GEN_BINARY):
|
$(DOCS_GEN_BINARY): cmd/docs-gen/main.go
|
||||||
go build -mod=vendor -o $@ k8s.io/kube-openapi/cmd/openapi-gen
|
go build -mod=vendor -o $@ ./cmd/docs-gen
|
||||||
|
|
||||||
$(GOLINT_BINARY):
|
$(STATICCHECK_BINARY):
|
||||||
go build -mod=vendor -o $@ golang.org/x/lint/golint
|
go build -mod=vendor -o $@ honnef.co/go/tools/cmd/staticcheck
|
||||||
|
|
||||||
$(EMBEDMD_BINARY):
|
$(EMBEDMD_BINARY):
|
||||||
go build -mod=vendor -o $@ github.com/campoy/embedmd
|
go build -mod=vendor -o $@ github.com/campoy/embedmd
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -6,17 +6,20 @@ Kilo is a multi-cloud network overlay built on WireGuard and designed for Kubern
|
|||||||
|
|
||||||
[](https://github.com/squat/kilo/actions?query=workflow%3ACI)
|
[](https://github.com/squat/kilo/actions?query=workflow%3ACI)
|
||||||
[](https://goreportcard.com/report/github.com/squat/kilo)
|
[](https://goreportcard.com/report/github.com/squat/kilo)
|
||||||
|
[](https://hub.docker.com/r/squat/kilo)
|
||||||
|
[](https://slack.k8s.io/)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Kilo connects nodes in a cluster by providing an encrypted layer 3 network that can span across data centers and public clouds.
|
Kilo connects nodes in a cluster by providing an encrypted layer 3 network that can span across data centers and public clouds.
|
||||||
|
The Pod network created by Kilo is always fully connected, even when the nodes are in different networks or behind NAT.
|
||||||
By allowing pools of nodes in different locations to communicate securely, Kilo enables the operation of multi-cloud clusters.
|
By allowing pools of nodes in different locations to communicate securely, Kilo enables the operation of multi-cloud clusters.
|
||||||
Kilo's design allows clients to VPN to a cluster in order to securely access services running on the cluster.
|
Kilo's design allows clients to VPN to a cluster in order to securely access services running on the cluster.
|
||||||
In addition to creating multi-cloud clusters, Kilo enables the creation of multi-cluster services, i.e. services that span across different Kubernetes clusters.
|
In addition to creating multi-cloud clusters, Kilo enables the creation of multi-cluster services, i.e. services that span across different Kubernetes clusters.
|
||||||
|
|
||||||
An introductory video about Kilo from KubeCon EU 2019 can be found on [youtube](https://www.youtube.com/watch?v=iPz_DAOOCKA).
|
An introductory video about Kilo from KubeCon EU 2019 can be found on [youtube](https://www.youtube.com/watch?v=iPz_DAOOCKA).
|
||||||
|
|
||||||
## How it works
|
## How It Works
|
||||||
|
|
||||||
Kilo uses [WireGuard](https://www.wireguard.com/), a performant and secure VPN, to create a mesh between the different nodes in a cluster.
|
Kilo uses [WireGuard](https://www.wireguard.com/), a performant and secure VPN, to create a mesh between the different nodes in a cluster.
|
||||||
The Kilo agent, `kg`, runs on every node in the cluster, setting up the public and private keys for the VPN as well as the necessary rules to route packets between locations.
|
The Kilo agent, `kg`, runs on every node in the cluster, setting up the public and private keys for the VPN as well as the necessary rules to route packets between locations.
|
||||||
@@ -69,24 +72,28 @@ Kilo can be installed by deploying a DaemonSet to the cluster.
|
|||||||
To run Kilo on kubeadm:
|
To run Kilo on kubeadm:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/crds.yaml
|
||||||
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-kubeadm.yaml
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-kubeadm.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
To run Kilo on bootkube:
|
To run Kilo on bootkube:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/crds.yaml
|
||||||
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-bootkube.yaml
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-bootkube.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
To run Kilo on Typhoon:
|
To run Kilo on Typhoon:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/crds.yaml
|
||||||
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-typhoon.yaml
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-typhoon.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
To run Kilo on k3s:
|
To run Kilo on k3s:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/crds.yaml
|
||||||
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-k3s.yaml
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-k3s.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -99,6 +106,7 @@ Kilo currently supports running on top of Flannel.
|
|||||||
For example, to run Kilo on a Typhoon cluster running Flannel:
|
For example, to run Kilo on a Typhoon cluster running Flannel:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/crds.yaml
|
||||||
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-typhoon-flannel.yaml
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-typhoon-flannel.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -107,7 +115,7 @@ kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kil
|
|||||||
## VPN
|
## VPN
|
||||||
|
|
||||||
Kilo also enables peers outside of a Kubernetes cluster to connect to the VPN, allowing cluster applications to securely access external services and permitting developers and support to securely debug cluster resources.
|
Kilo also enables peers outside of a Kubernetes cluster to connect to the VPN, allowing cluster applications to securely access external services and permitting developers and support to securely debug cluster resources.
|
||||||
In order to declare a peer, start by defining a Kilo peer resource:
|
In order to declare a peer, start by defining a Kilo Peer resource:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cat <<'EOF' | kubectl apply -f -
|
cat <<'EOF' | kubectl apply -f -
|
||||||
@@ -148,7 +156,7 @@ for n in $(kubectl --kubeconfig $KUBECONFIG2 get no -o name | cut -d'/' -f2); do
|
|||||||
kgctl --kubeconfig $KUBECONFIG2 showconf node $n --as-peer -o yaml --allowed-ips $SERVICECIDR2 | kubectl --kubeconfig $KUBECONFIG1 apply -f -
|
kgctl --kubeconfig $KUBECONFIG2 showconf node $n --as-peer -o yaml --allowed-ips $SERVICECIDR2 | kubectl --kubeconfig $KUBECONFIG1 apply -f -
|
||||||
done
|
done
|
||||||
# Create a Service in cluster2 to mirror the Service in cluster1.
|
# Create a Service in cluster2 to mirror the Service in cluster1.
|
||||||
cat <<'EOF' | kubectl --kubeconfig $KUBECONFIG2 apply -f -
|
cat <<EOF | kubectl --kubeconfig $KUBECONFIG2 apply -f -
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
|
|||||||
292
cmd/docs-gen/main.go
Normal file
292
cmd/docs-gen/main.go
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
// Copyright 2021 the Kilo authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// This file was adapted from https://github.com/prometheus-operator/prometheus-operator/blob/master/cmd/po-docgen/api.go.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/doc"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
firstParagraph = `# API
|
||||||
|
This document is a reference of the API types introduced by Kilo.
|
||||||
|
|
||||||
|
> **Note**: this document is generated from code comments. When contributing a change to this document, please do so by changing the code comments.`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
links = map[string]string{
|
||||||
|
"metav1.ObjectMeta": "https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#objectmeta-v1-meta",
|
||||||
|
"metav1.ListMeta": "https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#listmeta-v1-meta",
|
||||||
|
}
|
||||||
|
|
||||||
|
selfLinks = map[string]string{}
|
||||||
|
typesDoc = map[string]KubeTypes{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func toSectionLink(name string) string {
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
name = strings.Replace(name, " ", "-", -1)
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTOC(types []KubeTypes) {
|
||||||
|
fmt.Printf("\n## Table of Contents\n")
|
||||||
|
for _, t := range types {
|
||||||
|
strukt := t[0]
|
||||||
|
if len(t) > 1 {
|
||||||
|
fmt.Printf("* [%s](#%s)\n", strukt.Name, toSectionLink(strukt.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printAPIDocs(paths []string) {
|
||||||
|
fmt.Println(firstParagraph)
|
||||||
|
|
||||||
|
types := parseDocumentationFrom(paths)
|
||||||
|
for _, t := range types {
|
||||||
|
strukt := t[0]
|
||||||
|
selfLinks[strukt.Name] = "#" + strings.ToLower(strukt.Name)
|
||||||
|
typesDoc[toLink(strukt.Name)] = t[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to parse once more to now add the self links and the inlined fields
|
||||||
|
types = parseDocumentationFrom(paths)
|
||||||
|
|
||||||
|
printTOC(types)
|
||||||
|
|
||||||
|
for _, t := range types {
|
||||||
|
strukt := t[0]
|
||||||
|
if len(t) > 1 {
|
||||||
|
fmt.Printf("\n## %s\n\n%s\n\n", strukt.Name, strukt.Doc)
|
||||||
|
|
||||||
|
fmt.Println("| Field | Description | Scheme | Required |")
|
||||||
|
fmt.Println("| ----- | ----------- | ------ | -------- |")
|
||||||
|
fields := t[1:]
|
||||||
|
for _, f := range fields {
|
||||||
|
fmt.Println("|", f.Name, "|", f.Doc, "|", f.Type, "|", f.Mandatory, "|")
|
||||||
|
}
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println("[Back to TOC](#table-of-contents)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pair of strings. We need the name of fields and the doc.
|
||||||
|
type Pair struct {
|
||||||
|
Name, Doc, Type string
|
||||||
|
Mandatory bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// KubeTypes is an array to represent all available types in a parsed file. [0] is for the type itself
|
||||||
|
type KubeTypes []Pair
|
||||||
|
|
||||||
|
// parseDocumentationFrom gets all types' documentation and returns them as an
|
||||||
|
// array. Each type is again represented as an array (we have to use arrays as we
|
||||||
|
// need to be sure of the order of the fields). This function returns fields and
|
||||||
|
// struct definitions that have no documentation as {name, ""}.
|
||||||
|
func parseDocumentationFrom(srcs []string) []KubeTypes {
|
||||||
|
var docForTypes []KubeTypes
|
||||||
|
|
||||||
|
for _, src := range srcs {
|
||||||
|
pkg := astFrom(src)
|
||||||
|
|
||||||
|
for _, kubType := range pkg.Types {
|
||||||
|
if structType, ok := kubType.Decl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType); ok {
|
||||||
|
var ks KubeTypes
|
||||||
|
ks = append(ks, Pair{kubType.Name, fmtRawDoc(kubType.Doc), "", false})
|
||||||
|
|
||||||
|
for _, field := range structType.Fields.List {
|
||||||
|
// Skip fields that are not tagged.
|
||||||
|
if field.Tag == nil {
|
||||||
|
os.Stderr.WriteString(fmt.Sprintf("Tag is nil, skipping field: %v of type %v\n", field, field.Type))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Treat inlined fields separately as we don't want the original types to appear in the doc.
|
||||||
|
if isInlined(field) {
|
||||||
|
// Skip external types, as we don't want their content to be part of the API documentation.
|
||||||
|
if isInternalType(field.Type) {
|
||||||
|
ks = append(ks, typesDoc[fieldType(field.Type)]...)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
typeString := fieldType(field.Type)
|
||||||
|
fieldMandatory := fieldRequired(field)
|
||||||
|
if n := fieldName(field); n != "-" {
|
||||||
|
fieldDoc := fmtRawDoc(field.Doc.Text())
|
||||||
|
ks = append(ks, Pair{n, fieldDoc, typeString, fieldMandatory})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
docForTypes = append(docForTypes, ks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return docForTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
func astFrom(filePath string) *doc.Package {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
m := make(map[string]*ast.File)
|
||||||
|
|
||||||
|
f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m[filePath] = f
|
||||||
|
apkg, _ := ast.NewPackage(fset, m, nil, nil)
|
||||||
|
|
||||||
|
return doc.New(apkg, "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fmtRawDoc(rawDoc string) string {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
delPrevChar := func() {
|
||||||
|
if buffer.Len() > 0 {
|
||||||
|
buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore all lines after ---
|
||||||
|
rawDoc = strings.Split(rawDoc, "---")[0]
|
||||||
|
|
||||||
|
for _, line := range strings.Split(rawDoc, "\n") {
|
||||||
|
line = strings.TrimRight(line, " ")
|
||||||
|
leading := strings.TrimLeft(line, " ")
|
||||||
|
switch {
|
||||||
|
case len(line) == 0: // Keep paragraphs
|
||||||
|
delPrevChar()
|
||||||
|
buffer.WriteString("\n\n")
|
||||||
|
case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs
|
||||||
|
case strings.HasPrefix(leading, "+"): // Ignore instructions to go2idl
|
||||||
|
default:
|
||||||
|
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
|
||||||
|
delPrevChar()
|
||||||
|
line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..."
|
||||||
|
} else {
|
||||||
|
line += " "
|
||||||
|
}
|
||||||
|
buffer.WriteString(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postDoc := strings.TrimRight(buffer.String(), "\n")
|
||||||
|
postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to "
|
||||||
|
postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape "
|
||||||
|
postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
|
||||||
|
postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
|
||||||
|
postDoc = strings.Replace(postDoc, "|", "\\|", -1)
|
||||||
|
|
||||||
|
return postDoc
|
||||||
|
}
|
||||||
|
|
||||||
|
func toLink(typeName string) string {
|
||||||
|
selfLink, hasSelfLink := selfLinks[typeName]
|
||||||
|
if hasSelfLink {
|
||||||
|
return wrapInLink(typeName, selfLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
link, hasLink := links[typeName]
|
||||||
|
if hasLink {
|
||||||
|
return wrapInLink(typeName, link)
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeName
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapInLink(text, link string) string {
|
||||||
|
return fmt.Sprintf("[%s](%s)", text, link)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInlined(field *ast.Field) bool {
|
||||||
|
jsonTag := reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]).Get("json") // Delete first and last quotation
|
||||||
|
return strings.Contains(jsonTag, "inline")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInternalType(typ ast.Expr) bool {
|
||||||
|
switch typ := typ.(type) {
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
pkg := typ.X.(*ast.Ident)
|
||||||
|
return strings.HasPrefix(pkg.Name, "monitoring")
|
||||||
|
case *ast.StarExpr:
|
||||||
|
return isInternalType(typ.X)
|
||||||
|
case *ast.ArrayType:
|
||||||
|
return isInternalType(typ.Elt)
|
||||||
|
case *ast.MapType:
|
||||||
|
return isInternalType(typ.Key) && isInternalType(typ.Value)
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldName returns the name of the field as it should appear in JSON format
|
||||||
|
// "-" indicates that this field is not part of the JSON representation
|
||||||
|
func fieldName(field *ast.Field) string {
|
||||||
|
jsonTag := reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]).Get("json") // Delete first and last quotation
|
||||||
|
jsonTag = strings.Split(jsonTag, ",")[0] // This can return "-"
|
||||||
|
if jsonTag == "" {
|
||||||
|
if field.Names != nil {
|
||||||
|
return field.Names[0].Name
|
||||||
|
}
|
||||||
|
return field.Type.(*ast.Ident).Name
|
||||||
|
}
|
||||||
|
return jsonTag
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldRequired returns whether a field is a required field.
|
||||||
|
func fieldRequired(field *ast.Field) bool {
|
||||||
|
jsonTag := ""
|
||||||
|
if field.Tag != nil {
|
||||||
|
jsonTag = reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]).Get("json") // Delete first and last quotation
|
||||||
|
return !strings.Contains(jsonTag, "omitempty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func fieldType(typ ast.Expr) string {
|
||||||
|
switch typ := typ.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
return toLink(typ.Name)
|
||||||
|
case *ast.StarExpr:
|
||||||
|
return "*" + toLink(fieldType(typ.X))
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
pkg := typ.X.(*ast.Ident)
|
||||||
|
t := typ.Sel
|
||||||
|
return toLink(pkg.Name + "." + t.Name)
|
||||||
|
case *ast.ArrayType:
|
||||||
|
return "[]" + toLink(fieldType(typ.Elt))
|
||||||
|
case *ast.MapType:
|
||||||
|
return "map[" + toLink(fieldType(typ.Key)) + "]" + toLink(fieldType(typ.Value))
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
printAPIDocs(os.Args[1:])
|
||||||
|
}
|
||||||
147
cmd/kg/handlers.go
Normal file
147
cmd/kg/handlers.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
// Copyright 2021 the Kilo authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
|
"github.com/squat/kilo/pkg/mesh"
|
||||||
|
)
|
||||||
|
|
||||||
|
type graphHandler struct {
|
||||||
|
mesh *mesh.Mesh
|
||||||
|
granularity mesh.Granularity
|
||||||
|
hostname *string
|
||||||
|
subnet *net.IPNet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *graphHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ns, err := h.mesh.Nodes().List()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("failed to list nodes: %v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ps, err := h.mesh.Peers().List()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("failed to list peers: %v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes := make(map[string]*mesh.Node)
|
||||||
|
for _, n := range ns {
|
||||||
|
if n.Ready() {
|
||||||
|
nodes[n.Name] = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(nodes) == 0 {
|
||||||
|
http.Error(w, "did not find any valid Kilo nodes in the cluster", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peers := make(map[string]*mesh.Peer)
|
||||||
|
for _, p := range ps {
|
||||||
|
if p.Ready() {
|
||||||
|
peers[p.Name] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
topo, err := mesh.NewTopology(nodes, peers, h.granularity, *h.hostname, 0, wgtypes.Key{}, h.subnet, nodes[*h.hostname].PersistentKeepalive, nil)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("failed to create topology: %v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dot, err := topo.Dot()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("failed to generate graph: %v", err), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBufferString(dot)
|
||||||
|
|
||||||
|
format := r.URL.Query().Get("format")
|
||||||
|
switch format {
|
||||||
|
case "":
|
||||||
|
format = "svg"
|
||||||
|
case "dot", "gv":
|
||||||
|
// If the raw dot data is requested, return it as string.
|
||||||
|
// This allows client-side rendering rather than server-side.
|
||||||
|
w.Write(buf.Bytes())
|
||||||
|
return
|
||||||
|
|
||||||
|
case "svg", "png", "bmp", "fig", "gif", "json", "ps":
|
||||||
|
// Accepted format
|
||||||
|
|
||||||
|
default:
|
||||||
|
http.Error(w, "unsupported format", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
layout := r.URL.Query().Get("layout")
|
||||||
|
switch layout {
|
||||||
|
case "":
|
||||||
|
layout = "circo"
|
||||||
|
|
||||||
|
case "circo", "dot", "neato", "twopi", "fdp":
|
||||||
|
// Accepted layout
|
||||||
|
|
||||||
|
default:
|
||||||
|
http.Error(w, "unsupported layout", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
command := exec.Command("dot", "-K"+layout, "-T"+format)
|
||||||
|
command.Stderr = os.Stderr
|
||||||
|
|
||||||
|
stdin, err := command.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = io.Copy(stdin, buf); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = stdin.Close(); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := command.Output()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "unable to render graph", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mimeType := mime.TypeByExtension("." + format)
|
||||||
|
if mimeType == "" {
|
||||||
|
mimeType = "application/octet-stream"
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Add("content-type", mimeType)
|
||||||
|
w.Write(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func healthHandler(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
214
cmd/kg/main.go
214
cmd/kg/main.go
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2019 the Kilo authors
|
// Copyright 2021 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -27,10 +28,11 @@ import (
|
|||||||
|
|
||||||
"github.com/go-kit/kit/log"
|
"github.com/go-kit/kit/log"
|
||||||
"github.com/go-kit/kit/log/level"
|
"github.com/go-kit/kit/log/level"
|
||||||
|
"github.com/metalmatze/signal/internalserver"
|
||||||
"github.com/oklog/run"
|
"github.com/oklog/run"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||||
flag "github.com/spf13/pflag"
|
"github.com/spf13/cobra"
|
||||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
@@ -40,6 +42,7 @@ import (
|
|||||||
kiloclient "github.com/squat/kilo/pkg/k8s/clientset/versioned"
|
kiloclient "github.com/squat/kilo/pkg/k8s/clientset/versioned"
|
||||||
"github.com/squat/kilo/pkg/mesh"
|
"github.com/squat/kilo/pkg/mesh"
|
||||||
"github.com/squat/kilo/pkg/version"
|
"github.com/squat/kilo/pkg/version"
|
||||||
|
"github.com/squat/kilo/pkg/wireguard"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -77,51 +80,79 @@ var (
|
|||||||
}, ", ")
|
}, ", ")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main is the principal function for the binary, wrapped only by `main` for convenience.
|
var cmd = &cobra.Command{
|
||||||
func Main() error {
|
Use: "kg",
|
||||||
backend := flag.String("backend", k8s.Backend, fmt.Sprintf("The backend for the mesh. Possible values: %s", availableBackends))
|
Short: "kg is the Kilo agent",
|
||||||
cleanUpIface := flag.Bool("clean-up-interface", false, "Should Kilo delete its interface when it shuts down?")
|
Long: `kg is the Kilo agent.
|
||||||
createIface := flag.Bool("create-interface", true, "Should kilo create an interface on startup?")
|
It runs on every node of a cluster,
|
||||||
cni := flag.Bool("cni", true, "Should Kilo manage the node's CNI configuration?")
|
setting up the public and private keys for the VPN
|
||||||
cniPath := flag.String("cni-path", mesh.DefaultCNIPath, "Path to CNI config.")
|
as well as the necessary rules to route packets between locations.`,
|
||||||
compatibility := flag.String("compatibility", "", fmt.Sprintf("Should Kilo run in compatibility mode? Possible values: %s", availableCompatibilities))
|
PreRunE: preRun,
|
||||||
encapsulate := flag.String("encapsulate", string(encapsulation.Always), fmt.Sprintf("When should Kilo encapsulate packets within a location? Possible values: %s", availableEncapsulations))
|
RunE: runRoot,
|
||||||
granularity := flag.String("mesh-granularity", string(mesh.LogicalGranularity), fmt.Sprintf("The granularity of the network mesh to create. Possible values: %s", availableGranularities))
|
SilenceUsage: true,
|
||||||
kubeconfig := flag.String("kubeconfig", "", "Path to kubeconfig.")
|
SilenceErrors: true,
|
||||||
hostname := flag.String("hostname", "", "Hostname of the node on which this process is running.")
|
}
|
||||||
iface := flag.String("interface", mesh.DefaultKiloInterface, "Name of the Kilo interface to use; if it does not exist, it will be created.")
|
|
||||||
listen := flag.String("listen", ":1107", "The address at which to listen for health and metrics.")
|
|
||||||
local := flag.Bool("local", true, "Should Kilo manage routes within a location?")
|
|
||||||
logLevel := flag.String("log-level", logLevelInfo, fmt.Sprintf("Log level to use. Possible values: %s", availableLogLevels))
|
|
||||||
master := flag.String("master", "", "The address of the Kubernetes API server (overrides any value in kubeconfig).")
|
|
||||||
topologyLabel := flag.String("topology-label", k8s.RegionLabelKey, "Kubernetes node label used to group nodes into logical locations.")
|
|
||||||
var port uint
|
|
||||||
flag.UintVar(&port, "port", mesh.DefaultKiloPort, "The port over which WireGuard peers should communicate.")
|
|
||||||
subnet := flag.String("subnet", mesh.DefaultKiloSubnet.String(), "CIDR from which to allocate addresses for WireGuard interfaces.")
|
|
||||||
resyncPeriod := flag.Duration("resync-period", 30*time.Second, "How often should the Kilo controllers reconcile?")
|
|
||||||
printVersion := flag.Bool("version", false, "Print version and exit")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *printVersion {
|
var (
|
||||||
fmt.Println(version.Version)
|
backend string
|
||||||
return nil
|
cleanUpIface bool
|
||||||
}
|
createIface bool
|
||||||
|
cni bool
|
||||||
|
cniPath string
|
||||||
|
compatibility string
|
||||||
|
encapsulate string
|
||||||
|
granularity string
|
||||||
|
hostname string
|
||||||
|
kubeconfig string
|
||||||
|
iface string
|
||||||
|
listen string
|
||||||
|
local bool
|
||||||
|
master string
|
||||||
|
mtu uint
|
||||||
|
topologyLabel string
|
||||||
|
port int
|
||||||
|
subnet string
|
||||||
|
resyncPeriod time.Duration
|
||||||
|
iptablesForwardRule bool
|
||||||
|
prioritisePrivateAddr bool
|
||||||
|
|
||||||
_, s, err := net.ParseCIDR(*subnet)
|
printVersion bool
|
||||||
if err != nil {
|
logLevel string
|
||||||
return fmt.Errorf("failed to parse %q as CIDR: %v", *subnet, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *hostname == "" {
|
logger log.Logger
|
||||||
var err error
|
registry *prometheus.Registry
|
||||||
*hostname, err = os.Hostname()
|
)
|
||||||
if *hostname == "" || err != nil {
|
|
||||||
return errors.New("failed to determine hostname")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := log.NewJSONLogger(log.NewSyncWriter(os.Stdout))
|
func init() {
|
||||||
switch *logLevel {
|
cmd.Flags().StringVar(&backend, "backend", k8s.Backend, fmt.Sprintf("The backend for the mesh. Possible values: %s", availableBackends))
|
||||||
|
cmd.Flags().BoolVar(&cleanUpIface, "clean-up-interface", false, "Should Kilo delete its interface when it shuts down?")
|
||||||
|
cmd.Flags().BoolVar(&createIface, "create-interface", true, "Should kilo create an interface on startup?")
|
||||||
|
cmd.Flags().BoolVar(&cni, "cni", true, "Should Kilo manage the node's CNI configuration?")
|
||||||
|
cmd.Flags().StringVar(&cniPath, "cni-path", mesh.DefaultCNIPath, "Path to CNI config.")
|
||||||
|
cmd.Flags().StringVar(&compatibility, "compatibility", "", fmt.Sprintf("Should Kilo run in compatibility mode? Possible values: %s", availableCompatibilities))
|
||||||
|
cmd.Flags().StringVar(&encapsulate, "encapsulate", string(encapsulation.Always), fmt.Sprintf("When should Kilo encapsulate packets within a location? Possible values: %s", availableEncapsulations))
|
||||||
|
cmd.Flags().StringVar(&granularity, "mesh-granularity", string(mesh.LogicalGranularity), fmt.Sprintf("The granularity of the network mesh to create. Possible values: %s", availableGranularities))
|
||||||
|
cmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "Path to kubeconfig.")
|
||||||
|
cmd.Flags().StringVar(&hostname, "hostname", "", "Hostname of the node on which this process is running.")
|
||||||
|
cmd.Flags().StringVar(&iface, "interface", mesh.DefaultKiloInterface, "Name of the Kilo interface to use; if it does not exist, it will be created.")
|
||||||
|
cmd.Flags().StringVar(&listen, "listen", ":1107", "The address at which to listen for health and metrics.")
|
||||||
|
cmd.Flags().BoolVar(&local, "local", true, "Should Kilo manage routes within a location?")
|
||||||
|
cmd.Flags().StringVar(&master, "master", "", "The address of the Kubernetes API server (overrides any value in kubeconfig).")
|
||||||
|
cmd.Flags().UintVar(&mtu, "mtu", wireguard.DefaultMTU, "The MTU of the WireGuard interface created by Kilo.")
|
||||||
|
cmd.Flags().StringVar(&topologyLabel, "topology-label", k8s.RegionLabelKey, "Kubernetes node label used to group nodes into logical locations.")
|
||||||
|
cmd.Flags().IntVar(&port, "port", mesh.DefaultKiloPort, "The port over which WireGuard peers should communicate.")
|
||||||
|
cmd.Flags().StringVar(&subnet, "subnet", mesh.DefaultKiloSubnet.String(), "CIDR from which to allocate addresses for WireGuard interfaces.")
|
||||||
|
cmd.Flags().DurationVar(&resyncPeriod, "resync-period", 30*time.Second, "How often should the Kilo controllers reconcile?")
|
||||||
|
cmd.Flags().BoolVar(&iptablesForwardRule, "iptables-forward-rules", false, "Add default accept rules to the FORWARD chain in iptables. Warning: this may break firewalls with a deny all policy and is potentially insecure!")
|
||||||
|
cmd.Flags().BoolVar(&prioritisePrivateAddr, "prioritise-private-addresses", false, "Prefer to assign a private IP address to the node's endpoint.")
|
||||||
|
|
||||||
|
cmd.PersistentFlags().BoolVar(&printVersion, "version", false, "Print version and exit")
|
||||||
|
cmd.PersistentFlags().StringVar(&logLevel, "log-level", logLevelInfo, fmt.Sprintf("Log level to use. Possible values: %s", availableLogLevels))
|
||||||
|
}
|
||||||
|
|
||||||
|
func preRun(_ *cobra.Command, _ []string) error {
|
||||||
|
logger = log.NewJSONLogger(log.NewSyncWriter(os.Stdout))
|
||||||
|
switch logLevel {
|
||||||
case logLevelAll:
|
case logLevelAll:
|
||||||
logger = level.NewFilter(logger, level.AllowAll())
|
logger = level.NewFilter(logger, level.AllowAll())
|
||||||
case logLevelDebug:
|
case logLevelDebug:
|
||||||
@@ -135,78 +166,109 @@ func Main() error {
|
|||||||
case logLevelNone:
|
case logLevelNone:
|
||||||
logger = level.NewFilter(logger, level.AllowNone())
|
logger = level.NewFilter(logger, level.AllowNone())
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("log level %v unknown; possible values are: %s", *logLevel, availableLogLevels)
|
return fmt.Errorf("log level %v unknown; possible values are: %s", logLevel, availableLogLevels)
|
||||||
}
|
}
|
||||||
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
|
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
|
||||||
logger = log.With(logger, "caller", log.DefaultCaller)
|
logger = log.With(logger, "caller", log.DefaultCaller)
|
||||||
|
|
||||||
e := encapsulation.Strategy(*encapsulate)
|
registry = prometheus.NewRegistry()
|
||||||
|
registry.MustRegister(
|
||||||
|
collectors.NewGoCollector(),
|
||||||
|
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runRoot is the principal function for the binary.
|
||||||
|
func runRoot(_ *cobra.Command, _ []string) error {
|
||||||
|
if printVersion {
|
||||||
|
fmt.Println(version.Version)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, s, err := net.ParseCIDR(subnet)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse %q as CIDR: %v", subnet, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hostname == "" {
|
||||||
|
var err error
|
||||||
|
hostname, err = os.Hostname()
|
||||||
|
if hostname == "" || err != nil {
|
||||||
|
return errors.New("failed to determine hostname")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e := encapsulation.Strategy(encapsulate)
|
||||||
switch e {
|
switch e {
|
||||||
case encapsulation.Never:
|
case encapsulation.Never:
|
||||||
case encapsulation.CrossSubnet:
|
case encapsulation.CrossSubnet:
|
||||||
case encapsulation.Always:
|
case encapsulation.Always:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("encapsulation %v unknown; possible values are: %s", *encapsulate, availableEncapsulations)
|
return fmt.Errorf("encapsulation %v unknown; possible values are: %s", encapsulate, availableEncapsulations)
|
||||||
}
|
}
|
||||||
|
|
||||||
var enc encapsulation.Encapsulator
|
var enc encapsulation.Encapsulator
|
||||||
switch *compatibility {
|
switch compatibility {
|
||||||
case "flannel":
|
case "flannel":
|
||||||
enc = encapsulation.NewFlannel(e)
|
enc = encapsulation.NewFlannel(e)
|
||||||
|
case "cilium":
|
||||||
|
enc = encapsulation.NewCilium(e)
|
||||||
default:
|
default:
|
||||||
enc = encapsulation.NewIPIP(e)
|
enc = encapsulation.NewIPIP(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
gr := mesh.Granularity(*granularity)
|
gr := mesh.Granularity(granularity)
|
||||||
switch gr {
|
switch gr {
|
||||||
case mesh.LogicalGranularity:
|
case mesh.LogicalGranularity:
|
||||||
case mesh.FullGranularity:
|
case mesh.FullGranularity:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("mesh granularity %v unknown; possible values are: %s", *granularity, availableGranularities)
|
return fmt.Errorf("mesh granularity %v unknown; possible values are: %s", granularity, availableGranularities)
|
||||||
}
|
}
|
||||||
|
|
||||||
var b mesh.Backend
|
var b mesh.Backend
|
||||||
switch *backend {
|
switch backend {
|
||||||
case k8s.Backend:
|
case k8s.Backend:
|
||||||
config, err := clientcmd.BuildConfigFromFlags(*master, *kubeconfig)
|
config, err := clientcmd.BuildConfigFromFlags(master, kubeconfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create Kubernetes config: %v", err)
|
return fmt.Errorf("failed to create Kubernetes config: %v", err)
|
||||||
}
|
}
|
||||||
c := kubernetes.NewForConfigOrDie(config)
|
c := kubernetes.NewForConfigOrDie(config)
|
||||||
kc := kiloclient.NewForConfigOrDie(config)
|
kc := kiloclient.NewForConfigOrDie(config)
|
||||||
ec := apiextensions.NewForConfigOrDie(config)
|
ec := apiextensions.NewForConfigOrDie(config)
|
||||||
b = k8s.New(c, kc, ec, *topologyLabel)
|
b = k8s.New(c, kc, ec, topologyLabel, log.With(logger, "component", "k8s backend"))
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("backend %v unknown; possible values are: %s", *backend, availableBackends)
|
return fmt.Errorf("backend %v unknown; possible values are: %s", backend, availableBackends)
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := mesh.New(b, enc, gr, *hostname, uint32(port), s, *local, *cni, *cniPath, *iface, *cleanUpIface, *createIface, *resyncPeriod, log.With(logger, "component", "kilo"))
|
if port < 1 || port > 1<<16-1 {
|
||||||
|
return fmt.Errorf("invalid port: port mus be in range [%d:%d], but got %d", 1, 1<<16-1, port)
|
||||||
|
}
|
||||||
|
m, err := mesh.New(b, enc, gr, hostname, port, s, local, cni, cniPath, iface, cleanUpIface, createIface, mtu, resyncPeriod, prioritisePrivateAddr, iptablesForwardRule, log.With(logger, "component", "kilo"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create Kilo mesh: %v", err)
|
return fmt.Errorf("failed to create Kilo mesh: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r := prometheus.NewRegistry()
|
m.RegisterMetrics(registry)
|
||||||
r.MustRegister(
|
|
||||||
prometheus.NewGoCollector(),
|
|
||||||
prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}),
|
|
||||||
)
|
|
||||||
m.RegisterMetrics(r)
|
|
||||||
|
|
||||||
var g run.Group
|
var g run.Group
|
||||||
{
|
{
|
||||||
|
h := internalserver.NewHandler(
|
||||||
|
internalserver.WithName("Internal Kilo API"),
|
||||||
|
internalserver.WithPrometheusRegistry(registry),
|
||||||
|
internalserver.WithPProf(),
|
||||||
|
)
|
||||||
|
h.AddEndpoint("/health", "Exposes health checks", healthHandler)
|
||||||
|
h.AddEndpoint("/graph", "Exposes Kilo mesh topology graph", (&graphHandler{m, gr, &hostname, s}).ServeHTTP)
|
||||||
// Run the HTTP server.
|
// Run the HTTP server.
|
||||||
mux := http.NewServeMux()
|
l, err := net.Listen("tcp", listen)
|
||||||
mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
})
|
|
||||||
mux.Handle("/metrics", promhttp.HandlerFor(r, promhttp.HandlerOpts{}))
|
|
||||||
l, err := net.Listen("tcp", *listen)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to listen on %s: %v", *listen, err)
|
return fmt.Errorf("failed to listen on %s: %v", listen, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
g.Add(func() error {
|
g.Add(func() error {
|
||||||
if err := http.Serve(l, mux); err != nil && err != http.ErrServerClosed {
|
if err := http.Serve(l, h); err != nil && err != http.ErrServerClosed {
|
||||||
return fmt.Errorf("error: server exited unexpectedly: %v", err)
|
return fmt.Errorf("error: server exited unexpectedly: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -216,15 +278,16 @@ func Main() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
// Start the mesh.
|
// Start the mesh.
|
||||||
g.Add(func() error {
|
g.Add(func() error {
|
||||||
logger.Log("msg", fmt.Sprintf("Starting Kilo network mesh '%v'.", version.Version))
|
logger.Log("msg", fmt.Sprintf("Starting Kilo network mesh '%v'.", version.Version))
|
||||||
if err := m.Run(); err != nil {
|
if err := m.Run(ctx); err != nil {
|
||||||
return fmt.Errorf("error: Kilo exited unexpectedly: %v", err)
|
return fmt.Errorf("error: Kilo exited unexpectedly: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, func(error) {
|
}, func(error) {
|
||||||
m.Stop()
|
cancel()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,8 +314,15 @@ func Main() error {
|
|||||||
return g.Run()
|
return g.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var versionCmd = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "Print the version and exit.",
|
||||||
|
Run: func(_ *cobra.Command, _ []string) { fmt.Println(version.Version) },
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := Main(); err != nil {
|
cmd.AddCommand(webhookCmd, versionCmd)
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
273
cmd/kg/webhook.go
Normal file
273
cmd/kg/webhook.go
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
// Copyright 2021 the Kilo authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-kit/kit/log/level"
|
||||||
|
"github.com/oklog/run"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
v1 "k8s.io/api/admission/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
|
||||||
|
kilo "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||||
|
"github.com/squat/kilo/pkg/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
var webhookCmd = &cobra.Command{
|
||||||
|
Use: "webhook",
|
||||||
|
PreRunE: func(c *cobra.Command, a []string) error {
|
||||||
|
if c.HasParent() {
|
||||||
|
return c.Parent().PreRunE(c, a)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Short: "webhook starts a HTTPS server to validate updates and creations of Kilo peers.",
|
||||||
|
RunE: webhook,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
certPath string
|
||||||
|
keyPath string
|
||||||
|
metricsAddr string
|
||||||
|
listenAddr string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
webhookCmd.Flags().StringVar(&certPath, "cert-file", "", "The path to a certificate file")
|
||||||
|
webhookCmd.Flags().StringVar(&keyPath, "key-file", "", "The path to a key file")
|
||||||
|
webhookCmd.Flags().StringVar(&metricsAddr, "listen-metrics", ":1107", "The metrics server will be listening to that address")
|
||||||
|
webhookCmd.Flags().StringVar(&listenAddr, "listen", ":8443", "The webhook server will be listening to that address")
|
||||||
|
}
|
||||||
|
|
||||||
|
var deserializer = serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer()
|
||||||
|
|
||||||
|
var (
|
||||||
|
validationCounter = prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "admission_requests_total",
|
||||||
|
Help: "The number of received admission reviews requests",
|
||||||
|
},
|
||||||
|
[]string{"operation", "response"},
|
||||||
|
)
|
||||||
|
requestCounter = prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "http_requests_total",
|
||||||
|
Help: "The number of received http requests",
|
||||||
|
},
|
||||||
|
[]string{"handler", "method"},
|
||||||
|
)
|
||||||
|
errorCounter = prometheus.NewCounter(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "errors_total",
|
||||||
|
Help: "The total number of errors",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func validationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
level.Debug(logger).Log("msg", "handling request", "source", r.RemoteAddr)
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
errorCounter.Inc()
|
||||||
|
level.Error(logger).Log("err", "failed to parse body from incoming request", "source", r.RemoteAddr)
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var admissionReview v1.AdmissionReview
|
||||||
|
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
if contentType != "application/json" {
|
||||||
|
errorCounter.Inc()
|
||||||
|
msg := fmt.Sprintf("received Content-Type=%s, expected application/json", contentType)
|
||||||
|
level.Error(logger).Log("err", msg)
|
||||||
|
http.Error(w, msg, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := v1.AdmissionReview{}
|
||||||
|
|
||||||
|
_, gvk, err := deserializer.Decode(body, nil, &admissionReview)
|
||||||
|
if err != nil {
|
||||||
|
errorCounter.Inc()
|
||||||
|
msg := fmt.Sprintf("Request could not be decoded: %v", err)
|
||||||
|
level.Error(logger).Log("err", msg)
|
||||||
|
http.Error(w, msg, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *gvk != v1.SchemeGroupVersion.WithKind("AdmissionReview") {
|
||||||
|
errorCounter.Inc()
|
||||||
|
msg := "only API v1 is supported"
|
||||||
|
level.Error(logger).Log("err", msg)
|
||||||
|
http.Error(w, msg, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.SetGroupVersionKind(*gvk)
|
||||||
|
response.Response = &v1.AdmissionResponse{
|
||||||
|
UID: admissionReview.Request.UID,
|
||||||
|
}
|
||||||
|
|
||||||
|
rawExtension := admissionReview.Request.Object
|
||||||
|
var peer kilo.Peer
|
||||||
|
|
||||||
|
if err := json.Unmarshal(rawExtension.Raw, &peer); err != nil {
|
||||||
|
errorCounter.Inc()
|
||||||
|
msg := fmt.Sprintf("could not unmarshal extension to peer spec: %v:", err)
|
||||||
|
level.Error(logger).Log("err", msg)
|
||||||
|
http.Error(w, msg, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := peer.Validate(); err == nil {
|
||||||
|
level.Debug(logger).Log("msg", "got valid peer spec", "spec", peer.Spec, "name", peer.ObjectMeta.Name)
|
||||||
|
validationCounter.With(prometheus.Labels{"operation": string(admissionReview.Request.Operation), "response": "allowed"}).Inc()
|
||||||
|
response.Response.Allowed = true
|
||||||
|
} else {
|
||||||
|
level.Debug(logger).Log("msg", "got invalid peer spec", "spec", peer.Spec, "name", peer.ObjectMeta.Name)
|
||||||
|
validationCounter.With(prometheus.Labels{"operation": string(admissionReview.Request.Operation), "response": "denied"}).Inc()
|
||||||
|
response.Response.Result = &metav1.Status{
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
errorCounter.Inc()
|
||||||
|
msg := fmt.Sprintf("failed to marshal response: %v", err)
|
||||||
|
level.Error(logger).Log("err", msg)
|
||||||
|
http.Error(w, msg, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if _, err := w.Write(res); err != nil {
|
||||||
|
level.Error(logger).Log("err", err, "msg", "failed to write response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func metricsMiddleWare(path string, next func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requestCounter.With(prometheus.Labels{"method": r.Method, "handler": path}).Inc()
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func webhook(_ *cobra.Command, _ []string) error {
|
||||||
|
if printVersion {
|
||||||
|
fmt.Println(version.Version)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
registry.MustRegister(
|
||||||
|
errorCounter,
|
||||||
|
validationCounter,
|
||||||
|
requestCounter,
|
||||||
|
)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
var g run.Group
|
||||||
|
g.Add(run.SignalHandler(ctx, syscall.SIGINT, syscall.SIGTERM))
|
||||||
|
{
|
||||||
|
mm := http.NewServeMux()
|
||||||
|
mm.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
|
||||||
|
msrv := &http.Server{
|
||||||
|
Addr: metricsAddr,
|
||||||
|
Handler: mm,
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Add(
|
||||||
|
func() error {
|
||||||
|
level.Info(logger).Log("msg", "starting metrics server", "address", msrv.Addr)
|
||||||
|
err := msrv.ListenAndServe()
|
||||||
|
level.Info(logger).Log("msg", "metrics server exited", "err", err)
|
||||||
|
return err
|
||||||
|
|
||||||
|
},
|
||||||
|
func(err error) {
|
||||||
|
var serr run.SignalError
|
||||||
|
if ok := errors.As(err, &serr); ok {
|
||||||
|
level.Info(logger).Log("msg", "received signal", "signal", serr.Signal.String(), "err", err.Error())
|
||||||
|
} else {
|
||||||
|
level.Error(logger).Log("msg", "received error", "err", err.Error())
|
||||||
|
}
|
||||||
|
level.Info(logger).Log("msg", "shutting down metrics server gracefully")
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
if err := msrv.Shutdown(ctx); err != nil {
|
||||||
|
level.Error(logger).Log("msg", "failed to shut down metrics server gracefully", "err", err.Error())
|
||||||
|
msrv.Close()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/validate", metricsMiddleWare("/validate", validationHandler))
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: listenAddr,
|
||||||
|
Handler: mux,
|
||||||
|
}
|
||||||
|
g.Add(
|
||||||
|
func() error {
|
||||||
|
level.Info(logger).Log("msg", "starting webhook server", "address", srv.Addr)
|
||||||
|
err := srv.ListenAndServeTLS(certPath, keyPath)
|
||||||
|
level.Info(logger).Log("msg", "webhook server exited", "err", err)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
func(err error) {
|
||||||
|
var serr run.SignalError
|
||||||
|
if ok := errors.As(err, &serr); ok {
|
||||||
|
level.Info(logger).Log("msg", "received signal", "signal", serr.Signal.String(), "err", err.Error())
|
||||||
|
} else {
|
||||||
|
level.Error(logger).Log("msg", "received error", "err", err.Error())
|
||||||
|
}
|
||||||
|
level.Info(logger).Log("msg", "shutting down webhook server gracefully")
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
|
level.Error(logger).Log("msg", "failed to shut down webhook server gracefully", "err", err.Error())
|
||||||
|
srv.Close()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := g.Run()
|
||||||
|
var serr run.SignalError
|
||||||
|
if ok := errors.As(err, &serr); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
372
cmd/kgctl/connect_linux.go
Normal file
372
cmd/kgctl/connect_linux.go
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
// Copyright 2022 the Kilo authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-kit/kit/log"
|
||||||
|
"github.com/go-kit/kit/log/level"
|
||||||
|
"github.com/oklog/run"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"github.com/squat/kilo/pkg/iproute"
|
||||||
|
"github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||||
|
"github.com/squat/kilo/pkg/mesh"
|
||||||
|
"github.com/squat/kilo/pkg/route"
|
||||||
|
"github.com/squat/kilo/pkg/wireguard"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logLevel string
|
||||||
|
connectOpts struct {
|
||||||
|
allowedIP net.IPNet
|
||||||
|
allowedIPs []net.IPNet
|
||||||
|
privateKey string
|
||||||
|
cleanUp bool
|
||||||
|
mtu uint
|
||||||
|
resyncPeriod time.Duration
|
||||||
|
interfaceName string
|
||||||
|
persistentKeepalive int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func takeIPNet(_ net.IP, i *net.IPNet, err error) *net.IPNet {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func connect() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "connect",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: runConnect,
|
||||||
|
Short: "connect to a Kilo cluster as a peer over WireGuard",
|
||||||
|
SilenceUsage: true,
|
||||||
|
}
|
||||||
|
cmd.Flags().IPNetVarP(&connectOpts.allowedIP, "allowed-ip", "a", *takeIPNet(net.ParseCIDR("10.10.10.10/32")), "Allowed IP of the peer.")
|
||||||
|
cmd.Flags().StringSliceVar(&allowedIPs, "allowed-ips", []string{}, "Additional allowed IPs of the cluster, e.g. the service CIDR.")
|
||||||
|
cmd.Flags().StringVar(&logLevel, "log-level", logLevelInfo, fmt.Sprintf("Log level to use. Possible values: %s", availableLogLevels))
|
||||||
|
cmd.Flags().StringVar(&connectOpts.privateKey, "private-key", "", "Path to an existing WireGuard private key file.")
|
||||||
|
cmd.Flags().BoolVar(&connectOpts.cleanUp, "clean-up", true, "Should Kilo clean up the routes and interface when it shuts down?")
|
||||||
|
cmd.Flags().UintVar(&connectOpts.mtu, "mtu", uint(1420), "The MTU for the WireGuard interface.")
|
||||||
|
cmd.Flags().DurationVar(&connectOpts.resyncPeriod, "resync-period", 30*time.Second, "How often should Kilo reconcile?")
|
||||||
|
cmd.Flags().StringVarP(&connectOpts.interfaceName, "interface", "i", mesh.DefaultKiloInterface, "Name of the Kilo interface to use; if it does not exist, it will be created.")
|
||||||
|
cmd.Flags().IntVar(&connectOpts.persistentKeepalive, "persistent-keepalive", 10, "How often should WireGuard send keepalives? Setting to 0 will disable sending keepalives.")
|
||||||
|
|
||||||
|
availableLogLevels = strings.Join([]string{
|
||||||
|
logLevelAll,
|
||||||
|
logLevelDebug,
|
||||||
|
logLevelInfo,
|
||||||
|
logLevelWarn,
|
||||||
|
logLevelError,
|
||||||
|
logLevelNone,
|
||||||
|
}, ", ")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runConnect(cmd *cobra.Command, args []string) error {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
logger := log.NewJSONLogger(log.NewSyncWriter(os.Stdout))
|
||||||
|
switch logLevel {
|
||||||
|
case logLevelAll:
|
||||||
|
logger = level.NewFilter(logger, level.AllowAll())
|
||||||
|
case logLevelDebug:
|
||||||
|
logger = level.NewFilter(logger, level.AllowDebug())
|
||||||
|
case logLevelInfo:
|
||||||
|
logger = level.NewFilter(logger, level.AllowInfo())
|
||||||
|
case logLevelWarn:
|
||||||
|
logger = level.NewFilter(logger, level.AllowWarn())
|
||||||
|
case logLevelError:
|
||||||
|
logger = level.NewFilter(logger, level.AllowError())
|
||||||
|
case logLevelNone:
|
||||||
|
logger = level.NewFilter(logger, level.AllowNone())
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("log level %s unknown; possible values are: %s", logLevel, availableLogLevels)
|
||||||
|
}
|
||||||
|
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
|
||||||
|
logger = log.With(logger, "caller", log.DefaultCaller)
|
||||||
|
peerName := args[0]
|
||||||
|
|
||||||
|
for i := range allowedIPs {
|
||||||
|
_, aip, err := net.ParseCIDR(allowedIPs[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
connectOpts.allowedIPs = append(connectOpts.allowedIPs, *aip)
|
||||||
|
}
|
||||||
|
|
||||||
|
var privateKey wgtypes.Key
|
||||||
|
var err error
|
||||||
|
if connectOpts.privateKey == "" {
|
||||||
|
privateKey, err = wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate private key: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
raw, err := os.ReadFile(connectOpts.privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read private key: %w", err)
|
||||||
|
}
|
||||||
|
privateKey, err = wgtypes.ParseKey(string(raw))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse private key: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publicKey := privateKey.PublicKey()
|
||||||
|
level.Info(logger).Log("msg", "generated public key", "key", publicKey)
|
||||||
|
|
||||||
|
if _, err := opts.kc.KiloV1alpha1().Peers().Get(ctx, peerName, metav1.GetOptions{}); apierrors.IsNotFound(err) {
|
||||||
|
peer := &v1alpha1.Peer{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: peerName,
|
||||||
|
},
|
||||||
|
Spec: v1alpha1.PeerSpec{
|
||||||
|
AllowedIPs: []string{connectOpts.allowedIP.String()},
|
||||||
|
PersistentKeepalive: connectOpts.persistentKeepalive,
|
||||||
|
PublicKey: publicKey.String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if _, err := opts.kc.KiloV1alpha1().Peers().Create(ctx, peer, metav1.CreateOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("failed to create peer: %w", err)
|
||||||
|
}
|
||||||
|
level.Info(logger).Log("msg", "created peer", "peer", peerName)
|
||||||
|
if connectOpts.cleanUp {
|
||||||
|
defer func() {
|
||||||
|
ctxWithTimeout, cancelWithTimeout := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancelWithTimeout()
|
||||||
|
if err := opts.kc.KiloV1alpha1().Peers().Delete(ctxWithTimeout, peerName, metav1.DeleteOptions{}); err != nil {
|
||||||
|
level.Error(logger).Log("err", fmt.Sprintf("failed to delete peer: %v", err))
|
||||||
|
} else {
|
||||||
|
level.Info(logger).Log("msg", "deleted peer", "peer", peerName)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("failed to get peer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iface, _, err := wireguard.New(connectOpts.interfaceName, connectOpts.mtu)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create wg interface: %w", err)
|
||||||
|
}
|
||||||
|
level.Info(logger).Log("msg", "created WireGuard interface", "name", connectOpts.interfaceName, "index", iface)
|
||||||
|
|
||||||
|
table := route.NewTable()
|
||||||
|
if connectOpts.cleanUp {
|
||||||
|
defer cleanUp(iface, table, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := iproute.SetAddress(iface, &connectOpts.allowedIP); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
level.Info(logger).Log("msg", "set IP address of WireGuard interface", "IP", connectOpts.allowedIP.String())
|
||||||
|
|
||||||
|
if err := iproute.Set(iface, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var g run.Group
|
||||||
|
g.Add(run.SignalHandler(ctx, syscall.SIGINT, syscall.SIGTERM))
|
||||||
|
|
||||||
|
{
|
||||||
|
g.Add(
|
||||||
|
func() error {
|
||||||
|
errCh, err := table.Run(ctx.Done())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to watch for route table updates: %w", err)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case err, ok := <-errCh:
|
||||||
|
if ok {
|
||||||
|
level.Error(logger).Log("err", err.Error())
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
func(err error) {
|
||||||
|
cancel()
|
||||||
|
var serr run.SignalError
|
||||||
|
if ok := errors.As(err, &serr); ok {
|
||||||
|
level.Debug(logger).Log("msg", "received signal", "signal", serr.Signal.String(), "err", err.Error())
|
||||||
|
} else {
|
||||||
|
level.Error(logger).Log("msg", "received error", "err", err.Error())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
g.Add(
|
||||||
|
func() error {
|
||||||
|
level.Info(logger).Log("msg", "starting syncer")
|
||||||
|
for {
|
||||||
|
if err := sync(table, peerName, privateKey, iface, logger); err != nil {
|
||||||
|
level.Error(logger).Log("msg", "failed to sync", "err", err.Error())
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-time.After(connectOpts.resyncPeriod):
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, func(err error) {
|
||||||
|
cancel()
|
||||||
|
var serr run.SignalError
|
||||||
|
if ok := errors.As(err, &serr); ok {
|
||||||
|
level.Debug(logger).Log("msg", "received signal", "signal", serr.Signal.String(), "err", err.Error())
|
||||||
|
} else {
|
||||||
|
level.Error(logger).Log("msg", "received error", "err", err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.Run()
|
||||||
|
var serr run.SignalError
|
||||||
|
if ok := errors.As(err, &serr); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUp(iface int, t *route.Table, logger log.Logger) {
|
||||||
|
if err := iproute.Set(iface, false); err != nil {
|
||||||
|
level.Error(logger).Log("err", fmt.Sprintf("failed to set WireGuard interface down: %v", err))
|
||||||
|
}
|
||||||
|
if err := iproute.RemoveInterface(iface); err != nil {
|
||||||
|
level.Error(logger).Log("err", fmt.Sprintf("failed to remove WireGuard interface: %v", err))
|
||||||
|
}
|
||||||
|
if err := t.CleanUp(); err != nil {
|
||||||
|
level.Error(logger).Log("failed to clean up routes: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sync(table *route.Table, peerName string, privateKey wgtypes.Key, iface int, logger log.Logger) error {
|
||||||
|
ns, err := opts.backend.Nodes().List()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list nodes: %w", err)
|
||||||
|
}
|
||||||
|
for _, n := range ns {
|
||||||
|
_, err := n.Endpoint.UDPAddr(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ps, err := opts.backend.Peers().List()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list peers: %w", err)
|
||||||
|
}
|
||||||
|
// Obtain the Granularity by looking at the annotation of the first node.
|
||||||
|
if opts.granularity, err = determineGranularity(opts.granularity, ns); err != nil {
|
||||||
|
return fmt.Errorf("failed to determine granularity: %w", err)
|
||||||
|
}
|
||||||
|
var hostname string
|
||||||
|
var subnet *net.IPNet
|
||||||
|
nodes := make(map[string]*mesh.Node)
|
||||||
|
var nodeNames []string
|
||||||
|
for _, n := range ns {
|
||||||
|
if n.Ready() {
|
||||||
|
nodes[n.Name] = n
|
||||||
|
hostname = n.Name
|
||||||
|
nodeNames = append(nodeNames, n.Name)
|
||||||
|
}
|
||||||
|
if n.WireGuardIP != nil && subnet == nil {
|
||||||
|
subnet = n.WireGuardIP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(nodes) == 0 {
|
||||||
|
return errors.New("did not find any valid Kilo nodes in the cluster")
|
||||||
|
}
|
||||||
|
if subnet == nil {
|
||||||
|
return errors.New("did not find a valid Kilo subnet on any node")
|
||||||
|
}
|
||||||
|
subnet.IP = subnet.IP.Mask(subnet.Mask)
|
||||||
|
sort.Strings(nodeNames)
|
||||||
|
nodes[nodeNames[0]].AllowedLocationIPs = append(nodes[nodeNames[0]].AllowedLocationIPs, connectOpts.allowedIPs...)
|
||||||
|
peers := make(map[string]*mesh.Peer)
|
||||||
|
for _, p := range ps {
|
||||||
|
if p.Ready() {
|
||||||
|
peers[p.Name] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := peers[peerName]; !ok {
|
||||||
|
return fmt.Errorf("did not find any peer named %q in the cluster", peerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := mesh.NewTopology(nodes, peers, opts.granularity, hostname, opts.port, wgtypes.Key{}, subnet, *peers[peerName].PersistentKeepaliveInterval, logger)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create topology: %w", err)
|
||||||
|
}
|
||||||
|
conf := t.PeerConf(peerName)
|
||||||
|
conf.PrivateKey = &privateKey
|
||||||
|
conf.ListenPort = &opts.port
|
||||||
|
|
||||||
|
wgClient, err := wgctrl.New()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer wgClient.Close()
|
||||||
|
|
||||||
|
current, err := wgClient.Device(connectOpts.interfaceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var equal bool
|
||||||
|
var diff string
|
||||||
|
equal, diff = conf.Equal(current)
|
||||||
|
if !equal {
|
||||||
|
// If the key is empty, then it's the first time we are running
|
||||||
|
// so don't bother printing a diff.
|
||||||
|
if current.PrivateKey != [wgtypes.KeyLen]byte{} {
|
||||||
|
level.Info(logger).Log("msg", "WireGuard configurations are different", "diff", diff)
|
||||||
|
}
|
||||||
|
level.Debug(logger).Log("msg", "setting WireGuard config", "config", conf.WGConfig())
|
||||||
|
if err := wgClient.ConfigureDevice(connectOpts.interfaceName, conf.WGConfig()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := table.Set(t.PeerRoutes(peerName, iface, connectOpts.allowedIPs)); err != nil {
|
||||||
|
return fmt.Errorf("failed to update route table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 CNI authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,16 +12,24 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package ip
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"errors"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddDefaultRoute sets the default route on the given gateway.
|
func connect() *cobra.Command {
|
||||||
func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
|
cmd := &cobra.Command{
|
||||||
_, defNet, _ := net.ParseCIDR("0.0.0.0/0")
|
Use: "connect",
|
||||||
return AddRoute(defNet, gw, dev)
|
Short: "not supporred on this OS",
|
||||||
|
RunE: func(_ *cobra.Command, _ []string) error {
|
||||||
|
return errors.New("this command is not supported on this OS")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"github.com/squat/kilo/pkg/mesh"
|
"github.com/squat/kilo/pkg/mesh"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,12 +34,17 @@ func graph() *cobra.Command {
|
|||||||
func runGraph(_ *cobra.Command, _ []string) error {
|
func runGraph(_ *cobra.Command, _ []string) error {
|
||||||
ns, err := opts.backend.Nodes().List()
|
ns, err := opts.backend.Nodes().List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to list nodes: %v", err)
|
return fmt.Errorf("failed to list nodes: %w", err)
|
||||||
}
|
}
|
||||||
ps, err := opts.backend.Peers().List()
|
ps, err := opts.backend.Peers().List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to list peers: %v", err)
|
return fmt.Errorf("failed to list peers: %w", err)
|
||||||
}
|
}
|
||||||
|
// Obtain the Granularity by looking at the annotation of the first node.
|
||||||
|
if opts.granularity, err = determineGranularity(opts.granularity, ns); err != nil {
|
||||||
|
return fmt.Errorf("failed to determine granularity: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var hostname string
|
var hostname string
|
||||||
subnet := mesh.DefaultKiloSubnet
|
subnet := mesh.DefaultKiloSubnet
|
||||||
nodes := make(map[string]*mesh.Node)
|
nodes := make(map[string]*mesh.Node)
|
||||||
@@ -60,13 +67,13 @@ func runGraph(_ *cobra.Command, _ []string) error {
|
|||||||
peers[p.Name] = p
|
peers[p.Name] = p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t, err := mesh.NewTopology(nodes, peers, opts.granularity, hostname, 0, []byte{}, subnet, nodes[hostname].PersistentKeepalive)
|
t, err := mesh.NewTopology(nodes, peers, opts.granularity, hostname, 0, wgtypes.Key{}, subnet, nodes[hostname].PersistentKeepalive, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create topology: %v", err)
|
return fmt.Errorf("failed to create topology: %w", err)
|
||||||
}
|
}
|
||||||
g, err := t.Dot()
|
g, err := t.Dot()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to generate graph: %v", err)
|
return fmt.Errorf("failed to generate graph: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println(g)
|
fmt.Println(g)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -15,10 +15,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-kit/kit/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
@@ -46,6 +49,7 @@ var (
|
|||||||
availableGranularities = strings.Join([]string{
|
availableGranularities = strings.Join([]string{
|
||||||
string(mesh.LogicalGranularity),
|
string(mesh.LogicalGranularity),
|
||||||
string(mesh.FullGranularity),
|
string(mesh.FullGranularity),
|
||||||
|
string(mesh.AutoGranularity),
|
||||||
}, ", ")
|
}, ", ")
|
||||||
availableLogLevels = strings.Join([]string{
|
availableLogLevels = strings.Join([]string{
|
||||||
logLevelAll,
|
logLevelAll,
|
||||||
@@ -58,7 +62,8 @@ var (
|
|||||||
opts struct {
|
opts struct {
|
||||||
backend mesh.Backend
|
backend mesh.Backend
|
||||||
granularity mesh.Granularity
|
granularity mesh.Granularity
|
||||||
port uint32
|
kc kiloclient.Interface
|
||||||
|
port int
|
||||||
}
|
}
|
||||||
backend string
|
backend string
|
||||||
granularity string
|
granularity string
|
||||||
@@ -66,35 +71,40 @@ var (
|
|||||||
topologyLabel string
|
topologyLabel string
|
||||||
)
|
)
|
||||||
|
|
||||||
func runRoot(_ *cobra.Command, _ []string) error {
|
func runRoot(c *cobra.Command, _ []string) error {
|
||||||
|
if opts.port < 1 || opts.port > 1<<16-1 {
|
||||||
|
return fmt.Errorf("invalid port: port mus be in range [%d:%d], but got %d", 1, 1<<16-1, opts.port)
|
||||||
|
}
|
||||||
|
|
||||||
opts.granularity = mesh.Granularity(granularity)
|
opts.granularity = mesh.Granularity(granularity)
|
||||||
switch opts.granularity {
|
switch opts.granularity {
|
||||||
case mesh.LogicalGranularity:
|
case mesh.LogicalGranularity:
|
||||||
case mesh.FullGranularity:
|
case mesh.FullGranularity:
|
||||||
|
case mesh.AutoGranularity:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("mesh granularity %v unknown; posible values are: %s", granularity, availableGranularities)
|
return fmt.Errorf("mesh granularity %s unknown; posible values are: %s", granularity, availableGranularities)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch backend {
|
switch backend {
|
||||||
case k8s.Backend:
|
case k8s.Backend:
|
||||||
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create Kubernetes config: %v", err)
|
return fmt.Errorf("failed to create Kubernetes config: %w", err)
|
||||||
}
|
}
|
||||||
c := kubernetes.NewForConfigOrDie(config)
|
c := kubernetes.NewForConfigOrDie(config)
|
||||||
kc := kiloclient.NewForConfigOrDie(config)
|
opts.kc = kiloclient.NewForConfigOrDie(config)
|
||||||
ec := apiextensions.NewForConfigOrDie(config)
|
ec := apiextensions.NewForConfigOrDie(config)
|
||||||
opts.backend = k8s.New(c, kc, ec, topologyLabel)
|
opts.backend = k8s.New(c, opts.kc, ec, topologyLabel, log.NewNopLogger())
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("backend %v unknown; posible values are: %s", backend, availableBackends)
|
return fmt.Errorf("backend %s unknown; posible values are: %s", backend, availableBackends)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := opts.backend.Nodes().Init(make(chan struct{})); err != nil {
|
if err := opts.backend.Nodes().Init(c.Context()); err != nil {
|
||||||
return fmt.Errorf("failed to initialize node backend: %v", err)
|
return fmt.Errorf("failed to initialize node backend: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := opts.backend.Peers().Init(make(chan struct{})); err != nil {
|
if err := opts.backend.Peers().Init(c.Context()); err != nil {
|
||||||
return fmt.Errorf("failed to initialize peer backend: %v", err)
|
return fmt.Errorf("failed to initialize peer backend: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -106,16 +116,22 @@ func main() {
|
|||||||
Long: "",
|
Long: "",
|
||||||
PersistentPreRunE: runRoot,
|
PersistentPreRunE: runRoot,
|
||||||
Version: version.Version,
|
Version: version.Version,
|
||||||
|
SilenceErrors: true,
|
||||||
}
|
}
|
||||||
cmd.PersistentFlags().StringVar(&backend, "backend", k8s.Backend, fmt.Sprintf("The backend for the mesh. Possible values: %s", availableBackends))
|
cmd.PersistentFlags().StringVar(&backend, "backend", k8s.Backend, fmt.Sprintf("The backend for the mesh. Possible values: %s", availableBackends))
|
||||||
cmd.PersistentFlags().StringVar(&granularity, "mesh-granularity", string(mesh.LogicalGranularity), fmt.Sprintf("The granularity of the network mesh to create. Possible values: %s", availableGranularities))
|
cmd.PersistentFlags().StringVar(&granularity, "mesh-granularity", string(mesh.AutoGranularity), fmt.Sprintf("The granularity of the network mesh to create. Possible values: %s", availableGranularities))
|
||||||
cmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", os.Getenv("KUBECONFIG"), "Path to kubeconfig.")
|
defaultKubeconfig := os.Getenv("KUBECONFIG")
|
||||||
cmd.PersistentFlags().Uint32Var(&opts.port, "port", mesh.DefaultKiloPort, "The WireGuard port over which the nodes communicate.")
|
if _, err := os.Stat(defaultKubeconfig); os.IsNotExist(err) {
|
||||||
|
defaultKubeconfig = filepath.Join(os.Getenv("HOME"), ".kube/config")
|
||||||
|
}
|
||||||
|
cmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", defaultKubeconfig, "Path to kubeconfig.")
|
||||||
|
cmd.PersistentFlags().IntVar(&opts.port, "port", mesh.DefaultKiloPort, "The WireGuard port over which the nodes communicate.")
|
||||||
cmd.PersistentFlags().StringVar(&topologyLabel, "topology-label", k8s.RegionLabelKey, "Kubernetes node label used to group nodes into logical locations.")
|
cmd.PersistentFlags().StringVar(&topologyLabel, "topology-label", k8s.RegionLabelKey, "Kubernetes node label used to group nodes into logical locations.")
|
||||||
|
|
||||||
for _, subCmd := range []*cobra.Command{
|
for _, subCmd := range []*cobra.Command{
|
||||||
graph(),
|
graph(),
|
||||||
showConf(),
|
showConf(),
|
||||||
|
connect(),
|
||||||
} {
|
} {
|
||||||
cmd.AddCommand(subCmd)
|
cmd.AddCommand(subCmd)
|
||||||
}
|
}
|
||||||
@@ -125,3 +141,20 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func determineGranularity(gr mesh.Granularity, ns []*mesh.Node) (mesh.Granularity, error) {
|
||||||
|
if gr == mesh.AutoGranularity {
|
||||||
|
if len(ns) == 0 {
|
||||||
|
return gr, errors.New("could not get any nodes")
|
||||||
|
}
|
||||||
|
ret := mesh.Granularity(ns[0].Granularity)
|
||||||
|
switch ret {
|
||||||
|
case mesh.LogicalGranularity:
|
||||||
|
case mesh.FullGranularity:
|
||||||
|
default:
|
||||||
|
return ret, fmt.Errorf("mesh granularity %s is not supported", opts.granularity)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
return gr, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2019 the Kilo authors
|
// Copyright 2021 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -15,14 +15,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@@ -47,7 +48,7 @@ var (
|
|||||||
}, ", ")
|
}, ", ")
|
||||||
allowedIPs []string
|
allowedIPs []string
|
||||||
showConfOpts struct {
|
showConfOpts struct {
|
||||||
allowedIPs []*net.IPNet
|
allowedIPs []net.IPNet
|
||||||
serializer *json.Serializer
|
serializer *json.Serializer
|
||||||
output string
|
output string
|
||||||
asPeer bool
|
asPeer bool
|
||||||
@@ -82,14 +83,14 @@ func runShowConf(c *cobra.Command, args []string) error {
|
|||||||
case outputFormatYAML:
|
case outputFormatYAML:
|
||||||
showConfOpts.serializer = json.NewYAMLSerializer(json.DefaultMetaFactory, peerCreatorTyper{}, peerCreatorTyper{})
|
showConfOpts.serializer = json.NewYAMLSerializer(json.DefaultMetaFactory, peerCreatorTyper{}, peerCreatorTyper{})
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("output format %v unknown; posible values are: %s", showConfOpts.output, availableOutputFormats)
|
return fmt.Errorf("output format %s unknown; posible values are: %s", showConfOpts.output, availableOutputFormats)
|
||||||
}
|
}
|
||||||
for i := range allowedIPs {
|
for i := range allowedIPs {
|
||||||
_, aip, err := net.ParseCIDR(allowedIPs[i])
|
_, aip, err := net.ParseCIDR(allowedIPs[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("allowed-ips must contain only valid CIDRs; got %q", allowedIPs[i])
|
return fmt.Errorf("allowed-ips must contain only valid CIDRs; got %q", allowedIPs[i])
|
||||||
}
|
}
|
||||||
showConfOpts.allowedIPs = append(showConfOpts.allowedIPs, aip)
|
showConfOpts.allowedIPs = append(showConfOpts.allowedIPs, *aip)
|
||||||
}
|
}
|
||||||
return runRoot(c, args)
|
return runRoot(c, args)
|
||||||
}
|
}
|
||||||
@@ -115,11 +116,15 @@ func showConfPeer() *cobra.Command {
|
|||||||
func runShowConfNode(_ *cobra.Command, args []string) error {
|
func runShowConfNode(_ *cobra.Command, args []string) error {
|
||||||
ns, err := opts.backend.Nodes().List()
|
ns, err := opts.backend.Nodes().List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to list nodes: %v", err)
|
return fmt.Errorf("failed to list nodes: %w", err)
|
||||||
}
|
}
|
||||||
ps, err := opts.backend.Peers().List()
|
ps, err := opts.backend.Peers().List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to list peers: %v", err)
|
return fmt.Errorf("failed to list peers: %w", err)
|
||||||
|
}
|
||||||
|
// Obtain the Granularity by looking at the annotation of the first node.
|
||||||
|
if opts.granularity, err = determineGranularity(opts.granularity, ns); err != nil {
|
||||||
|
return fmt.Errorf("failed to determine granularity: %w", err)
|
||||||
}
|
}
|
||||||
hostname := args[0]
|
hostname := args[0]
|
||||||
subnet := mesh.DefaultKiloSubnet
|
subnet := mesh.DefaultKiloSubnet
|
||||||
@@ -147,14 +152,14 @@ func runShowConfNode(_ *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := mesh.NewTopology(nodes, peers, opts.granularity, hostname, opts.port, []byte{}, subnet, nodes[hostname].PersistentKeepalive)
|
t, err := mesh.NewTopology(nodes, peers, opts.granularity, hostname, int(opts.port), wgtypes.Key{}, subnet, nodes[hostname].PersistentKeepalive, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create topology: %v", err)
|
return fmt.Errorf("failed to create topology: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var found bool
|
var found bool
|
||||||
for _, p := range t.PeerConf("").Peers {
|
for _, p := range t.PeerConf("").Peers {
|
||||||
if bytes.Equal(p.PublicKey, nodes[hostname].Key) {
|
if p.PublicKey == nodes[hostname].Key {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -167,7 +172,7 @@ func runShowConfNode(_ *cobra.Command, args []string) error {
|
|||||||
if !showConfOpts.asPeer {
|
if !showConfOpts.asPeer {
|
||||||
c, err := t.Conf().Bytes()
|
c, err := t.Conf().Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to generate configuration: %v", err)
|
return fmt.Errorf("failed to generate configuration: %w", err)
|
||||||
}
|
}
|
||||||
_, err = os.Stdout.Write(c)
|
_, err = os.Stdout.Write(c)
|
||||||
return err
|
return err
|
||||||
@@ -178,6 +183,9 @@ func runShowConfNode(_ *cobra.Command, args []string) error {
|
|||||||
fallthrough
|
fallthrough
|
||||||
case outputFormatYAML:
|
case outputFormatYAML:
|
||||||
p := t.AsPeer()
|
p := t.AsPeer()
|
||||||
|
if p == nil {
|
||||||
|
return errors.New("cannot generate config from nil peer")
|
||||||
|
}
|
||||||
p.AllowedIPs = append(p.AllowedIPs, showConfOpts.allowedIPs...)
|
p.AllowedIPs = append(p.AllowedIPs, showConfOpts.allowedIPs...)
|
||||||
p.DeduplicateIPs()
|
p.DeduplicateIPs()
|
||||||
k8sp := translatePeer(p)
|
k8sp := translatePeer(p)
|
||||||
@@ -185,13 +193,16 @@ func runShowConfNode(_ *cobra.Command, args []string) error {
|
|||||||
return showConfOpts.serializer.Encode(k8sp, os.Stdout)
|
return showConfOpts.serializer.Encode(k8sp, os.Stdout)
|
||||||
case outputFormatWireGuard:
|
case outputFormatWireGuard:
|
||||||
p := t.AsPeer()
|
p := t.AsPeer()
|
||||||
|
if p == nil {
|
||||||
|
return errors.New("cannot generate config from nil peer")
|
||||||
|
}
|
||||||
p.AllowedIPs = append(p.AllowedIPs, showConfOpts.allowedIPs...)
|
p.AllowedIPs = append(p.AllowedIPs, showConfOpts.allowedIPs...)
|
||||||
p.DeduplicateIPs()
|
p.DeduplicateIPs()
|
||||||
c, err := (&wireguard.Conf{
|
c, err := (&wireguard.Conf{
|
||||||
Peers: []*wireguard.Peer{p},
|
Peers: []wireguard.Peer{*p},
|
||||||
}).Bytes()
|
}).Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to generate configuration: %v", err)
|
return fmt.Errorf("failed to generate configuration: %w", err)
|
||||||
}
|
}
|
||||||
_, err = os.Stdout.Write(c)
|
_, err = os.Stdout.Write(c)
|
||||||
return err
|
return err
|
||||||
@@ -202,11 +213,15 @@ func runShowConfNode(_ *cobra.Command, args []string) error {
|
|||||||
func runShowConfPeer(_ *cobra.Command, args []string) error {
|
func runShowConfPeer(_ *cobra.Command, args []string) error {
|
||||||
ns, err := opts.backend.Nodes().List()
|
ns, err := opts.backend.Nodes().List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to list nodes: %v", err)
|
return fmt.Errorf("failed to list nodes: %w", err)
|
||||||
}
|
}
|
||||||
ps, err := opts.backend.Peers().List()
|
ps, err := opts.backend.Peers().List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to list peers: %v", err)
|
return fmt.Errorf("failed to list peers: %w", err)
|
||||||
|
}
|
||||||
|
// Obtain the Granularity by looking at the annotation of the first node.
|
||||||
|
if opts.granularity, err = determineGranularity(opts.granularity, ns); err != nil {
|
||||||
|
return fmt.Errorf("failed to determine granularity: %w", err)
|
||||||
}
|
}
|
||||||
var hostname string
|
var hostname string
|
||||||
subnet := mesh.DefaultKiloSubnet
|
subnet := mesh.DefaultKiloSubnet
|
||||||
@@ -236,14 +251,18 @@ func runShowConfPeer(_ *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("did not find any peer named %q in the cluster", peer)
|
return fmt.Errorf("did not find any peer named %q in the cluster", peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := mesh.NewTopology(nodes, peers, opts.granularity, hostname, mesh.DefaultKiloPort, []byte{}, subnet, peers[peer].PersistentKeepalive)
|
pka := time.Duration(0)
|
||||||
|
if p := peers[peer].PersistentKeepaliveInterval; p != nil {
|
||||||
|
pka = *p
|
||||||
|
}
|
||||||
|
t, err := mesh.NewTopology(nodes, peers, opts.granularity, hostname, mesh.DefaultKiloPort, wgtypes.Key{}, subnet, pka, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create topology: %v", err)
|
return fmt.Errorf("failed to create topology: %w", err)
|
||||||
}
|
}
|
||||||
if !showConfOpts.asPeer {
|
if !showConfOpts.asPeer {
|
||||||
c, err := t.PeerConf(peer).Bytes()
|
c, err := t.PeerConf(peer).Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to generate configuration: %v", err)
|
return fmt.Errorf("failed to generate configuration: %w", err)
|
||||||
}
|
}
|
||||||
_, err = os.Stdout.Write(c)
|
_, err = os.Stdout.Write(c)
|
||||||
return err
|
return err
|
||||||
@@ -264,10 +283,10 @@ func runShowConfPeer(_ *cobra.Command, args []string) error {
|
|||||||
p.AllowedIPs = append(p.AllowedIPs, showConfOpts.allowedIPs...)
|
p.AllowedIPs = append(p.AllowedIPs, showConfOpts.allowedIPs...)
|
||||||
p.DeduplicateIPs()
|
p.DeduplicateIPs()
|
||||||
c, err := (&wireguard.Conf{
|
c, err := (&wireguard.Conf{
|
||||||
Peers: []*wireguard.Peer{p},
|
Peers: []wireguard.Peer{*p},
|
||||||
}).Bytes()
|
}).Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to generate configuration: %v", err)
|
return fmt.Errorf("failed to generate configuration: %w", err)
|
||||||
}
|
}
|
||||||
_, err = os.Stdout.Write(c)
|
_, err = os.Stdout.Write(c)
|
||||||
return err
|
return err
|
||||||
@@ -276,6 +295,7 @@ func runShowConfPeer(_ *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// translatePeer translates a wireguard.Peer to a Peer CRD.
|
// translatePeer translates a wireguard.Peer to a Peer CRD.
|
||||||
|
// TODO this function has many similarities to peerBackend.Set(name, peer)
|
||||||
func translatePeer(peer *wireguard.Peer) *v1alpha1.Peer {
|
func translatePeer(peer *wireguard.Peer) *v1alpha1.Peer {
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
return &v1alpha1.Peer{}
|
return &v1alpha1.Peer{}
|
||||||
@@ -283,36 +303,33 @@ func translatePeer(peer *wireguard.Peer) *v1alpha1.Peer {
|
|||||||
var aips []string
|
var aips []string
|
||||||
for _, aip := range peer.AllowedIPs {
|
for _, aip := range peer.AllowedIPs {
|
||||||
// Skip any invalid IPs.
|
// Skip any invalid IPs.
|
||||||
if aip == nil {
|
// TODO all IPs should be valid, so no need to skip here?
|
||||||
|
if aip.String() == (&net.IPNet{}).String() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
aips = append(aips, aip.String())
|
aips = append(aips, aip.String())
|
||||||
}
|
}
|
||||||
var endpoint *v1alpha1.PeerEndpoint
|
var endpoint *v1alpha1.PeerEndpoint
|
||||||
if peer.Endpoint != nil && peer.Endpoint.Port > 0 && (peer.Endpoint.IP != nil || peer.Endpoint.DNS != "") {
|
if peer.Endpoint.Port() > 0 || !peer.Endpoint.HasDNS() {
|
||||||
var ip string
|
|
||||||
if peer.Endpoint.IP != nil {
|
|
||||||
ip = peer.Endpoint.IP.String()
|
|
||||||
}
|
|
||||||
endpoint = &v1alpha1.PeerEndpoint{
|
endpoint = &v1alpha1.PeerEndpoint{
|
||||||
DNSOrIP: v1alpha1.DNSOrIP{
|
DNSOrIP: v1alpha1.DNSOrIP{
|
||||||
DNS: peer.Endpoint.DNS,
|
IP: peer.Endpoint.IP().String(),
|
||||||
IP: ip,
|
DNS: peer.Endpoint.DNS(),
|
||||||
},
|
},
|
||||||
Port: peer.Endpoint.Port,
|
Port: uint32(peer.Endpoint.Port()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var key string
|
var key string
|
||||||
if len(peer.PublicKey) > 0 {
|
if peer.PublicKey != (wgtypes.Key{}) {
|
||||||
key = string(peer.PublicKey)
|
key = peer.PublicKey.String()
|
||||||
}
|
}
|
||||||
var psk string
|
var psk string
|
||||||
if len(peer.PresharedKey) > 0 {
|
if peer.PresharedKey != nil {
|
||||||
psk = string(peer.PresharedKey)
|
psk = peer.PresharedKey.String()
|
||||||
}
|
}
|
||||||
var pka int
|
var pka int
|
||||||
if peer.PersistentKeepalive > 0 {
|
if peer.PersistentKeepaliveInterval != nil && *peer.PersistentKeepaliveInterval > time.Duration(0) {
|
||||||
pka = peer.PersistentKeepalive
|
pka = int(*peer.PersistentKeepaliveInterval)
|
||||||
}
|
}
|
||||||
return &v1alpha1.Peer{
|
return &v1alpha1.Peer{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ The following annotations can be added to any Kubernetes Node object to configur
|
|||||||
|[kilo.squat.ai/leader](#leader)|string|`""`, `true`|
|
|[kilo.squat.ai/leader](#leader)|string|`""`, `true`|
|
||||||
|[kilo.squat.ai/location](#location)|string|`gcp-east`, `lab`|
|
|[kilo.squat.ai/location](#location)|string|`gcp-east`, `lab`|
|
||||||
|[kilo.squat.ai/persistent-keepalive](#persistent-keepalive)|uint|`10`|
|
|[kilo.squat.ai/persistent-keepalive](#persistent-keepalive)|uint|`10`|
|
||||||
|
|[kilo.squat.ai/allowed-location-ips](#allowed-location-ips)|CIDR|`66.66.66.66/32`|
|
||||||
|
|
||||||
### force-endpoint
|
### force-endpoint
|
||||||
In order to create links between locations, Kilo requires at least one node in each location to have an endpoint, ie a `host:port` combination, that is routable from the other locations.
|
In order to create links between locations, Kilo requires at least one node in each location to have an endpoint, ie a `host:port` combination, that is routable from the other locations.
|
||||||
@@ -35,7 +36,7 @@ In some situations it may be desirable to manually select the leader for a locat
|
|||||||
* _firewall_: Kilo requires an open UDP port, which defaults to 51820, to communicate between locations; if only one node is configured to have that port open, then that node should be given the leader annotation;
|
* _firewall_: Kilo requires an open UDP port, which defaults to 51820, to communicate between locations; if only one node is configured to have that port open, then that node should be given the leader annotation;
|
||||||
* _bandwidth_: if certain nodes in the cluster have a higher bandwidth or lower latency Internet connection, then those nodes should be given the leader annotation.
|
* _bandwidth_: if certain nodes in the cluster have a higher bandwidth or lower latency Internet connection, then those nodes should be given the leader annotation.
|
||||||
|
|
||||||
_Note_: multiple nodes within a single location can be given the leader annotation; in this case, Kilo will select one leader from the set of annotated nodes.
|
> **Note**: multiple nodes within a single location can be given the leader annotation; in this case, Kilo will select one leader from the set of annotated nodes.
|
||||||
|
|
||||||
### location
|
### location
|
||||||
Kilo allows nodes in different logical or physical locations to route packets to one-another.
|
Kilo allows nodes in different logical or physical locations to route packets to one-another.
|
||||||
@@ -43,7 +44,7 @@ In order to know what connections to create, Kilo needs to know which nodes are
|
|||||||
Kilo will try to infer each node's location from the [topology.kubernetes.io/region](https://kubernetes.io/docs/reference/kubernetes-api/labels-annotations-taints/#topologykubernetesioregion) node label.
|
Kilo will try to infer each node's location from the [topology.kubernetes.io/region](https://kubernetes.io/docs/reference/kubernetes-api/labels-annotations-taints/#topologykubernetesioregion) node label.
|
||||||
If the label is not present for a node, for example if running a bare-metal cluster or on an unsupported cloud provider, then the location annotation should be specified.
|
If the label is not present for a node, for example if running a bare-metal cluster or on an unsupported cloud provider, then the location annotation should be specified.
|
||||||
|
|
||||||
_Note_: all nodes without a defined location will be considered to be in the default location `""`.
|
> **Note**: all nodes without a defined location will be considered to be in the default location `""`.
|
||||||
|
|
||||||
### persistent-keepalive
|
### persistent-keepalive
|
||||||
In certain deployments, cluster nodes may be located behind NAT or a firewall, e.g. edge nodes located behind a commodity router.
|
In certain deployments, cluster nodes may be located behind NAT or a firewall, e.g. edge nodes located behind a commodity router.
|
||||||
@@ -52,3 +53,10 @@ In order for a node behind NAT to receive packets from nodes outside of the NATe
|
|||||||
The frequency of emission of these keepalive packets can be controlled by setting the persistent-keepalive annotation on the node behind NAT.
|
The frequency of emission of these keepalive packets can be controlled by setting the persistent-keepalive annotation on the node behind NAT.
|
||||||
The annotated node will use the specified value will as the persistent-keepalive interval for all of its peers.
|
The annotated node will use the specified value will as the persistent-keepalive interval for all of its peers.
|
||||||
For more background, [see the WireGuard documentation on NAT and firewall traversal](https://www.wireguard.com/quickstart/#nat-and-firewall-traversal-persistence).
|
For more background, [see the WireGuard documentation on NAT and firewall traversal](https://www.wireguard.com/quickstart/#nat-and-firewall-traversal-persistence).
|
||||||
|
|
||||||
|
### allowed-location-ips
|
||||||
|
It is possible to add allowed-location-ips to a location by annotating any node within that location.
|
||||||
|
Adding allowed-location-ips to a location makes these IPs routable from other locations as well.
|
||||||
|
|
||||||
|
In an example deployment of Kilo with two locations A and B, a printer in location A can be accessible from nodes and pods in location B.
|
||||||
|
Additionally, Kilo Peers can use the printer in location A.
|
||||||
|
|||||||
69
docs/api.md
Normal file
69
docs/api.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# API
|
||||||
|
This document is a reference of the API types introduced by Kilo.
|
||||||
|
|
||||||
|
> **Note**: this document is generated from code comments. When contributing a change to this document, please do so by changing the code comments.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
* [DNSOrIP](#dnsorip)
|
||||||
|
* [Peer](#peer)
|
||||||
|
* [PeerEndpoint](#peerendpoint)
|
||||||
|
* [PeerList](#peerlist)
|
||||||
|
* [PeerSpec](#peerspec)
|
||||||
|
|
||||||
|
## DNSOrIP
|
||||||
|
|
||||||
|
DNSOrIP represents either a DNS name or an IP address. When both are given, the IP address, as it is more specific, override the DNS name.
|
||||||
|
|
||||||
|
| Field | Description | Scheme | Required |
|
||||||
|
| ----- | ----------- | ------ | -------- |
|
||||||
|
| dns | DNS must be a valid RFC 1123 subdomain. | string | false |
|
||||||
|
| ip | IP must be a valid IP address. | string | false |
|
||||||
|
|
||||||
|
[Back to TOC](#table-of-contents)
|
||||||
|
|
||||||
|
## Peer
|
||||||
|
|
||||||
|
Peer is a WireGuard peer that should have access to the VPN.
|
||||||
|
|
||||||
|
| Field | Description | Scheme | Required |
|
||||||
|
| ----- | ----------- | ------ | -------- |
|
||||||
|
| metadata | Standard object’s metadata. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata | [metav1.ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#objectmeta-v1-meta) | false |
|
||||||
|
| spec | Specification of the desired behavior of the Kilo Peer. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status | [PeerSpec](#peerspec) | true |
|
||||||
|
|
||||||
|
[Back to TOC](#table-of-contents)
|
||||||
|
|
||||||
|
## PeerEndpoint
|
||||||
|
|
||||||
|
PeerEndpoint represents a WireGuard endpoint, which is an IP:port tuple.
|
||||||
|
|
||||||
|
| Field | Description | Scheme | Required |
|
||||||
|
| ----- | ----------- | ------ | -------- |
|
||||||
|
| dnsOrIP | DNSOrIP is a DNS name or an IP address. | [DNSOrIP](#dnsorip) | true |
|
||||||
|
| port | Port must be a valid port number. | uint32 | true |
|
||||||
|
|
||||||
|
[Back to TOC](#table-of-contents)
|
||||||
|
|
||||||
|
## PeerList
|
||||||
|
|
||||||
|
PeerList is a list of peers.
|
||||||
|
|
||||||
|
| Field | Description | Scheme | Required |
|
||||||
|
| ----- | ----------- | ------ | -------- |
|
||||||
|
| metadata | Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | [metav1.ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#listmeta-v1-meta) | false |
|
||||||
|
| items | List of peers. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md | [][Peer](#peer) | true |
|
||||||
|
|
||||||
|
[Back to TOC](#table-of-contents)
|
||||||
|
|
||||||
|
## PeerSpec
|
||||||
|
|
||||||
|
PeerSpec is the description and configuration of a peer.
|
||||||
|
|
||||||
|
| Field | Description | Scheme | Required |
|
||||||
|
| ----- | ----------- | ------ | -------- |
|
||||||
|
| allowedIPs | AllowedIPs is the list of IP addresses that are allowed for the given peer's tunnel. | []string | true |
|
||||||
|
| endpoint | Endpoint is the initial endpoint for connections to the peer. | *[PeerEndpoint](#peerendpoint) | false |
|
||||||
|
| persistentKeepalive | PersistentKeepalive is the interval in seconds of the emission of keepalive packets by the peer. This defaults to 0, which disables the feature. | int | false |
|
||||||
|
| presharedKey | PresharedKey is the optional symmetric encryption key for the peer. | string | false |
|
||||||
|
| publicKey | PublicKey is the WireGuard public key for the peer. | string | true |
|
||||||
|
|
||||||
|
[Back to TOC](#table-of-contents)
|
||||||
100
docs/building_kilo.md
Normal file
100
docs/building_kilo.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Build and Test Kilo
|
||||||
|
|
||||||
|
This document describes how you can build and test Kilo.
|
||||||
|
|
||||||
|
To follow along, you need to install the following utilities:
|
||||||
|
- `go` not for building but formatting the code and running unit tests
|
||||||
|
- `make`
|
||||||
|
- `jq`
|
||||||
|
- `git`
|
||||||
|
- `curl`
|
||||||
|
- `docker`
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Clone the Repository and `cd` into it.
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/squat/kilo.git
|
||||||
|
cd kilo
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
For consistency, the Kilo binaries are compiled in a Docker container, so make sure the `docker` package is installed and the daemon is running.
|
||||||
|
|
||||||
|
### Compile Binaries
|
||||||
|
|
||||||
|
To compile the `kg` and `kgctl` binaries run:
|
||||||
|
```shell
|
||||||
|
make
|
||||||
|
```
|
||||||
|
Binaries are always placed in a directory corresponding to the local system's OS and architecture following the pattern `bin/<os>/<architecture>/`, so on an AMD64 machine running Linux, the binaries will be stored in `bin/linux/amd64/`.
|
||||||
|
|
||||||
|
You can build the binaries for a different architecture by setting the `ARCH` environment variable before invoking `make`, e.g.:
|
||||||
|
```shell
|
||||||
|
ARCH=<arm|arm64|amd64> make
|
||||||
|
```
|
||||||
|
|
||||||
|
Likewise, to build `kg` for another OS, set the `OS` environment variable before invoking `make`:
|
||||||
|
```shell
|
||||||
|
OS=<windows|darwin|linux> make
|
||||||
|
```
|
||||||
|
## Test
|
||||||
|
|
||||||
|
To execute the unit tests, run:
|
||||||
|
```shell
|
||||||
|
make unit
|
||||||
|
```
|
||||||
|
|
||||||
|
To lint the code in the repository, run:
|
||||||
|
```shell
|
||||||
|
make lint
|
||||||
|
```
|
||||||
|
|
||||||
|
To execute basic end to end tests, run:
|
||||||
|
```shell
|
||||||
|
make e2e
|
||||||
|
```
|
||||||
|
> **Note**: The end to end tests are currently flaky, so try running them again if they fail.
|
||||||
|
|
||||||
|
To instead run all of the tests with a single command, run:
|
||||||
|
```shell
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build and Push the Container Images
|
||||||
|
|
||||||
|
If you want to build containers for a processor architecture that is different from your computer's, then you will first need to configure QEMU as the interpreter for binaries built for non-native architectures:
|
||||||
|
```shell
|
||||||
|
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the `$IMAGE` environment variable to `<your Docker Hub user name>/kilo`.
|
||||||
|
This way the generated container images and manifests will be named accordingly.
|
||||||
|
By skipping this step, you will be able to tag images but will not be able to push the containers and manifests to your own Docker Hub.
|
||||||
|
```shell
|
||||||
|
export IMAGE=<docker hub user name>/kilo
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to use a different container registry, run:
|
||||||
|
```shell
|
||||||
|
export REGISTRY=<your registry without a trailing slash>
|
||||||
|
```
|
||||||
|
|
||||||
|
To build containers with the `kg` image for `arm`, `arm64` and `amd64`, run:
|
||||||
|
```shell
|
||||||
|
make all-container
|
||||||
|
```
|
||||||
|
|
||||||
|
Push the container images and build a manifest with:
|
||||||
|
```shell
|
||||||
|
make manifest
|
||||||
|
```
|
||||||
|
|
||||||
|
To tag and push the manifest with `latest`, run:
|
||||||
|
```shell
|
||||||
|
make manifest-latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can deploy the custom build of Kilo to your cluster.
|
||||||
|
If you are already running Kilo, change the image from `squat/kilo` to `[registry/]<username>/kilo[:sha]`.
|
||||||
44
docs/building_website.md
Normal file
44
docs/building_website.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Build and Test the Website
|
||||||
|
|
||||||
|
You may have noticed that the `markdown` files in the `/docs` directory are also displayed on [Kilo's website](https://kilo.squat.ai/).
|
||||||
|
If you want to add documentation to Kilo, you can start a local webserver to check out how the website would look like.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Install [yarn](https://yarnpkg.com/getting-started/install).
|
||||||
|
|
||||||
|
## Build and Run
|
||||||
|
|
||||||
|
The markdown files for the website are located in `/website/docs` and are generated from the like-named markdown files in the `/docs` directory and from the corresponding header files without the `.md` extension in the `/website/docs` directory.
|
||||||
|
To generate the markdown files in `/website/docs`, run:
|
||||||
|
```shell
|
||||||
|
make website/docs/README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, build the website itself by installing the `node_modules` and building the website's HTML from the generated markdown:
|
||||||
|
```shell
|
||||||
|
make website/build/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, start the website server with:
|
||||||
|
```shell
|
||||||
|
yarn --cwd website start
|
||||||
|
```
|
||||||
|
This command should have opened a browser window with the website; if not, open your browser and point it to `http://localhost:3000`.
|
||||||
|
|
||||||
|
If you make changes to any of the markdown files in `/docs` and want to reload the local `node` server, run:
|
||||||
|
```shell
|
||||||
|
make website/docs/README.md -B
|
||||||
|
```
|
||||||
|
|
||||||
|
You can execute the above while the node server is running and the website will be rebuilt and reloaded automatically.
|
||||||
|
|
||||||
|
## Add a New File to the Docs
|
||||||
|
|
||||||
|
If you add a new file to the `/docs` directory, you also need to create a corresponding header file containing the front-matter in `/website/docs/`.
|
||||||
|
Then, regenerate the markdown for the website with the command:
|
||||||
|
```shell
|
||||||
|
make website/docs/README.md
|
||||||
|
```
|
||||||
|
Edit `/website/sidebars.js` accordingly.
|
||||||
|
> **Note**: The `id` in the header file `/website/docs/<new file>` must match the `id` specified in `website/sidebars.js`.
|
||||||
962
docs/grafana/kilo.json
Normal file
962
docs/grafana/kilo.json
Normal file
@@ -0,0 +1,962 @@
|
|||||||
|
{
|
||||||
|
"__inputs": [
|
||||||
|
{
|
||||||
|
"name": "DS_PROMETHEUS",
|
||||||
|
"label": "prometheus",
|
||||||
|
"description": "",
|
||||||
|
"type": "datasource",
|
||||||
|
"pluginId": "prometheus",
|
||||||
|
"pluginName": "Prometheus"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"__requires": [
|
||||||
|
{
|
||||||
|
"type": "grafana",
|
||||||
|
"id": "grafana",
|
||||||
|
"name": "Grafana",
|
||||||
|
"version": "7.5.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "panel",
|
||||||
|
"id": "graph",
|
||||||
|
"name": "Graph",
|
||||||
|
"version": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "datasource",
|
||||||
|
"id": "prometheus",
|
||||||
|
"name": "Prometheus",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "panel",
|
||||||
|
"id": "stat",
|
||||||
|
"name": "Stat",
|
||||||
|
"version": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": "-- Grafana --",
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"gnetId": null,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "Bps"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"fill": 1,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 12,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"alertThreshold": true
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pluginVersion": "7.5.4",
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "sum by (pod) (rate(wireguard_received_bytes_total[1h])) + sum by (pod) (rate(wireguard_sent_bytes_total[1h]))",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"queryType": "randomWalk",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:64",
|
||||||
|
"colorMode": "background6",
|
||||||
|
"fill": true,
|
||||||
|
"fillColor": "rgba(234, 112, 112, 0.12)",
|
||||||
|
"line": false,
|
||||||
|
"lineColor": "rgba(237, 46, 24, 0.60)",
|
||||||
|
"op": "time"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Throughput",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:42",
|
||||||
|
"format": "Bps",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:43",
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"fill": 1,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 10,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"alertThreshold": true
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pluginVersion": "7.5.4",
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "(sum(rate(wireguard_sent_bytes_total[5m])) - sum(rate(wireguard_received_bytes_total[5m])))/(sum(rate(wireguard_sent_bytes_total[5m])) + sum(rate(wireguard_received_bytes_total[5m])))",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"queryType": "randomWalk",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Slip (send - received)",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:502",
|
||||||
|
"format": "percentunit",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:503",
|
||||||
|
"format": "Bps",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"fill": 1,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 8
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 16,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"alertThreshold": true
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pluginVersion": "7.5.4",
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "sum by (public_key) (time() - (wireguard_latest_handshake_seconds!=0))",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"queryType": "randomWalk",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "latest handshake",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:219",
|
||||||
|
"format": "s",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": "1000",
|
||||||
|
"min": "0",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:220",
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"fill": 1,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 8
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 18,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"alertThreshold": true
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pluginVersion": "7.5.4",
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "sum by (instance) (rate(kilo_reconciles_total[30m]))",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"queryType": "randomWalk",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "kilo reconciles",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:539",
|
||||||
|
"decimals": null,
|
||||||
|
"format": "hertz",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:540",
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 4,
|
||||||
|
"x": 0,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"text": {},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "7.5.4",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "avg(kilo_peers)",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"queryType": "randomWalk",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Kilo Peers",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 4,
|
||||||
|
"x": 4,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"text": {},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "7.5.4",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "avg(kilo_nodes)",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"queryType": "randomWalk",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Kilo Nodes",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 4,
|
||||||
|
"x": 8,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"text": {},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "7.5.4",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "sum(kilo_leader)",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"queryType": "randomWalk",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "segments",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"fill": 1,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 6,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"alertThreshold": true
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pluginVersion": "7.5.4",
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "sum by (instance) (rate(kilo_errors_total[10m]))",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"queryType": "randomWalk",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Kilo Errors",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:446",
|
||||||
|
"format": "hertz",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:447",
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"fill": 1,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 24
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 20,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"alertThreshold": true
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pluginVersion": "7.5.4",
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "sum by (instance) (rate(process_cpu_seconds_total{pod=~\"kilo-.*\"}[1m]))",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"queryType": "randomWalk",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "CPU usage",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:162",
|
||||||
|
"format": "percentunit",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:163",
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"fill": 1,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 24
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 22,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"alertThreshold": true
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pluginVersion": "7.5.4",
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "sum by (instance) (process_resident_memory_bytes{pod=~\"kilo-.*\"})",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"queryType": "randomWalk",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Memory Allocation",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:231",
|
||||||
|
"format": "decbytes",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$$hashKey": "object:232",
|
||||||
|
"format": "decmbytes",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"datasource": null,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"id": 14,
|
||||||
|
"panels": [],
|
||||||
|
"title": "Row title",
|
||||||
|
"type": "row"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": false,
|
||||||
|
"schemaVersion": 27,
|
||||||
|
"style": "dark",
|
||||||
|
"tags": [],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-24h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Kilo",
|
||||||
|
"uid": "R8Lja3H7z",
|
||||||
|
"version": 11
|
||||||
|
}
|
||||||
BIN
docs/graphs/kilo.png
Normal file
BIN
docs/graphs/kilo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 543 KiB |
62
docs/kg.md
62
docs/kg.md
@@ -16,25 +16,45 @@ The behavior of `kg` can be configured using the command line flags listed below
|
|||||||
|
|
||||||
[embedmd]:# (../tmp/help.txt)
|
[embedmd]:# (../tmp/help.txt)
|
||||||
```txt
|
```txt
|
||||||
Usage of bin//linux/amd64/kg:
|
kg is the Kilo agent.
|
||||||
--backend string The backend for the mesh. Possible values: kubernetes (default "kubernetes")
|
It runs on every node of a cluster,
|
||||||
--clean-up-interface Should Kilo delete its interface when it shuts down?
|
setting up the public and private keys for the VPN
|
||||||
--cni Should Kilo manage the node's CNI configuration? (default true)
|
as well as the necessary rules to route packets between locations.
|
||||||
--cni-path string Path to CNI config. (default "/etc/cni/net.d/10-kilo.conflist")
|
|
||||||
--compatibility string Should Kilo run in compatibility mode? Possible values: flannel
|
Usage:
|
||||||
--create-interface Should kilo create an interface on startup? (default true)
|
kg [flags]
|
||||||
--encapsulate string When should Kilo encapsulate packets within a location? Possible values: never, crosssubnet, always (default "always")
|
kg [command]
|
||||||
--hostname string Hostname of the node on which this process is running.
|
|
||||||
--interface string Name of the Kilo interface to use; if it does not exist, it will be created. (default "kilo0")
|
Available Commands:
|
||||||
--kubeconfig string Path to kubeconfig.
|
completion generate the autocompletion script for the specified shell
|
||||||
--listen string The address at which to listen for health and metrics. (default ":1107")
|
help Help about any command
|
||||||
--local Should Kilo manage routes within a location? (default true)
|
version Print the version and exit.
|
||||||
--log-level string Log level to use. Possible values: all, debug, info, warn, error, none (default "info")
|
webhook webhook starts a HTTPS server to validate updates and creations of Kilo peers.
|
||||||
--master string The address of the Kubernetes API server (overrides any value in kubeconfig).
|
|
||||||
--mesh-granularity string The granularity of the network mesh to create. Possible values: location, full (default "location")
|
Flags:
|
||||||
--port uint The port over which WireGuard peers should communicate. (default 51820)
|
--backend string The backend for the mesh. Possible values: kubernetes (default "kubernetes")
|
||||||
--resync-period duration How often should the Kilo controllers reconcile? (default 30s)
|
--clean-up-interface Should Kilo delete its interface when it shuts down?
|
||||||
--subnet string CIDR from which to allocate addresses for WireGuard interfaces. (default "10.4.0.0/16")
|
--cni Should Kilo manage the node's CNI configuration? (default true)
|
||||||
--topology-label string Kubernetes node label used to group nodes into logical locations. (default "topology.kubernetes.io/region")
|
--cni-path string Path to CNI config. (default "/etc/cni/net.d/10-kilo.conflist")
|
||||||
--version Print version and exit
|
--compatibility string Should Kilo run in compatibility mode? Possible values: flannel
|
||||||
|
--create-interface Should kilo create an interface on startup? (default true)
|
||||||
|
--encapsulate string When should Kilo encapsulate packets within a location? Possible values: never, crosssubnet, always (default "always")
|
||||||
|
-h, --help help for kg
|
||||||
|
--hostname string Hostname of the node on which this process is running.
|
||||||
|
--interface string Name of the Kilo interface to use; if it does not exist, it will be created. (default "kilo0")
|
||||||
|
--iptables-forward-rules Add default accept rules to the FORWARD chain in iptables. Warning: this may break firewalls with a deny all policy and is potentially insecure!
|
||||||
|
--kubeconfig string Path to kubeconfig.
|
||||||
|
--listen string The address at which to listen for health and metrics. (default ":1107")
|
||||||
|
--local Should Kilo manage routes within a location? (default true)
|
||||||
|
--log-level string Log level to use. Possible values: all, debug, info, warn, error, none (default "info")
|
||||||
|
--master string The address of the Kubernetes API server (overrides any value in kubeconfig).
|
||||||
|
--mesh-granularity string The granularity of the network mesh to create. Possible values: location, full (default "location")
|
||||||
|
--mtu uint The MTU of the WireGuard interface created by Kilo. (default 1420)
|
||||||
|
--port int The port over which WireGuard peers should communicate. (default 51820)
|
||||||
|
--prioritise-private-addresses Prefer to assign a private IP address to the node's endpoint.
|
||||||
|
--resync-period duration How often should the Kilo controllers reconcile? (default 30s)
|
||||||
|
--subnet string CIDR from which to allocate addresses for WireGuard interfaces. (default "10.4.0.0/16")
|
||||||
|
--topology-label string Kubernetes node label used to group nodes into logical locations. (default "topology.kubernetes.io/region")
|
||||||
|
--version Print version and exit
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -6,21 +6,95 @@ This tool can be used to understand a mesh's topology, get the WireGuard configu
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Installing `kgctl` currently requires building the binary from source.
|
The `kgctl` binary is automatically compiled for Linux, macOS, and Windows for every release of Kilo and can be downloaded from [the GitHub releases page](https://github.com/squat/kilo/releases/latest).
|
||||||
*Note*: the [Go toolchain must be installed](https://golang.org/doc/install) in order to build the binary.
|
|
||||||
To build and install `kgctl`, run:
|
### Building from Source
|
||||||
|
Kilo is written in Golang and as a result the [Go toolchain must be installed](https://golang.org/doc/install) in order to build the `kgctl` binary.
|
||||||
|
To download the Kilo source code and then build and install `kgctl` using the latest commit all with a single command, run:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go install github.com/squat/kilo/cmd/kgctl
|
go install github.com/squat/kilo/cmd/kgctl@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, `kgctl` can be built and installed based on specific version of the code by specifying a Git tag or hash, e.g.:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go install github.com/squat/kilo/cmd/kgctl@0.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
When working on Kilo locally, it can be helpful to build and test the `kgctl` binary as part of the development cycle.
|
||||||
|
In order to build a binary from a local checkout of the Git repository, run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
This will produce a `kgctl` binary at `./bin/<your-os>/<your-architecture>/kgctl`.
|
||||||
|
|
||||||
|
|
||||||
|
### Binary Packages
|
||||||
|
|
||||||
|
#### Arch Linux
|
||||||
|
|
||||||
|
Install `kgctl` from the Arch User Repository using an AUR helper like `paru` or `yay`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
paru -S kgctl-bin
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arkade
|
||||||
|
|
||||||
|
The [arkade](https://github.com/alexellis/arkade) CLI can be used to install `kgctl` on any OS and architecture:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
arkade get kgctl
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
|Command|Syntax|Description|
|
|Command|Syntax|Description|
|
||||||
|----|----|-------|
|
|----|----|-------|
|
||||||
|
|[connect](#connect)|`kgctl connect <peer-name> [flags]`|Connect the host to the cluster, setting up the required interfaces, routes, and keys.|
|
||||||
|[graph](#graph)|`kgctl graph [flags]`|Produce a graph in GraphViz format representing the topology of the cluster.|
|
|[graph](#graph)|`kgctl graph [flags]`|Produce a graph in GraphViz format representing the topology of the cluster.|
|
||||||
|[showconf](#showconf)|`kgctl showconf ( node \| peer ) NAME [flags]`|Show the WireGuard configuration for a node or peer in the mesh.|
|
|[showconf](#showconf)|`kgctl showconf ( node \| peer ) <name> [flags]`|Show the WireGuard configuration for a node or peer in the mesh.|
|
||||||
|
|
||||||
|
### connect
|
||||||
|
|
||||||
|
The `connect` command configures the local host as a WireGuard Peer of the cluster and applies all of the necessary networking configuration to connect to the cluster.
|
||||||
|
As long as the process is running, it will watch the cluster for changes and automatically manage the configuration for new or updated Peers and Nodes.
|
||||||
|
If the given Peer name does not exist in the cluster, the command will register a new Peer and generate the necessary WireGuard keys.
|
||||||
|
When the command exits, all of the configuration, including newly registered Peers, is cleaned up.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
PEER_NAME=laptop
|
||||||
|
SERVICECIDR=10.43.0.0/16
|
||||||
|
kgctl connect $PEER_NAME --allowed-ips $SERVICECIDR
|
||||||
|
```
|
||||||
|
|
||||||
|
The local host is now connected to the cluster and all IPs from the cluster and any registered Peers are fully routable.
|
||||||
|
When combined with the `--clean-up false` flag, the configuration produced by the command is persistent and will remain in effect even after the process is stopped.
|
||||||
|
|
||||||
|
With the service CIDR of the cluster routable from the local host, Kubernetes DNS names can now be resolved by the cluster DNS provider.
|
||||||
|
For example, the following snippet could be used to resolve the clusterIP of the Kubernetes API:
|
||||||
|
```shell
|
||||||
|
dig @$(kubectl get service -n kube-system kube-dns -o=jsonpath='{.spec.clusterIP}') kubernetes.default.svc.cluster.local +short
|
||||||
|
# > 10.43.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
For convenience, the cluster DNS provider's IP address can be configured as the local host's DNS server, making Kubernetes DNS names easily resolvable.
|
||||||
|
For example, if using `systemd-resolved`, the following snippet could be used:
|
||||||
|
```shell
|
||||||
|
systemd-resolve --interface kilo0 --set-dns $(kubectl get service -n kube-system kube-dns -o=jsonpath='{.spec.clusterIP}') --set-domain cluster.local
|
||||||
|
# Now all lookups for DNS names ending in `.cluster.local` will be routed over the `kilo0` interface to the cluster DNS provider.
|
||||||
|
dig kubernetes.default.svc.cluster.local +short
|
||||||
|
# > 10.43.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: The `connect` command is currently only supported on Linux.
|
||||||
|
|
||||||
|
> **Note**: The `connect` command requires the `CAP_NET_ADMIN` capability in order to configure the host's networking stack; unprivileged users will need to use `sudo` or similar tools.
|
||||||
|
|
||||||
### graph
|
### graph
|
||||||
|
|
||||||
|
|||||||
100
docs/monitoring.md
Normal file
100
docs/monitoring.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Monitoring
|
||||||
|
|
||||||
|
The following assumes that you have applied the [kube-prometheus](https://github.com/prometheus-operator/kube-prometheus) monitoring stack onto your cluster.
|
||||||
|
|
||||||
|
## Kilo
|
||||||
|
|
||||||
|
Monitor the Kilo DaemonSet with:
|
||||||
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/podmonitor.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## WireGuard
|
||||||
|
|
||||||
|
Monitor the WireGuard interfaces with:
|
||||||
|
```shell
|
||||||
|
kubectl create ns kilo
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/wg-exporter.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
The manifest will deploy the [Prometheus WireGuard Exporter](https://github.com/MindFlavor/prometheus_wireguard_exporter) as a DaemonSet and a [PodMonitor](https://docs.openshift.com/container-platform/4.8/rest_api/monitoring_apis/podmonitor-monitoring-coreos-com-v1.html).
|
||||||
|
|
||||||
|
By default the kube-prometheus stack only monitors the `default`, `kube-system` and `monitoring` namespaces.
|
||||||
|
In order to allow Prometheus to monitor the `kilo` namespace, apply the Role and RoleBinding with:
|
||||||
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/wg-exporter-role-kube-prometheus.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
|
||||||
|
### Kilo
|
||||||
|
|
||||||
|
Kilo exports some standard metrics with the Prometheus GoCollector and ProcessCollector.
|
||||||
|
It also exposes some Kilo-specific metrics.
|
||||||
|
|
||||||
|
```
|
||||||
|
# HELP kilo_errors_total Number of errors that occurred while administering the mesh.
|
||||||
|
# TYPE kilo_errors_total counter
|
||||||
|
|
||||||
|
# HELP kilo_leader Leadership status of the node.
|
||||||
|
# TYPE kilo_leader gauge
|
||||||
|
|
||||||
|
# HELP kilo_nodes Number of nodes in the mesh.
|
||||||
|
# TYPE kilo_nodes gauge
|
||||||
|
|
||||||
|
# HELP kilo_peers Number of peers in the mesh.
|
||||||
|
# TYPE kilo_peers gauge
|
||||||
|
|
||||||
|
# HELP kilo_reconciles_total Number of reconciliation attempts.
|
||||||
|
# TYPE kilo_reconciles_total counter
|
||||||
|
```
|
||||||
|
|
||||||
|
### WireGuard
|
||||||
|
|
||||||
|
The [Prometheus WireGuard Exporter](https://github.com/MindFlavor/prometheus_wireguard_exporter) exports the following metrics:
|
||||||
|
|
||||||
|
```
|
||||||
|
# HELP wireguard_sent_bytes_total Bytes sent to the peer
|
||||||
|
# TYPE wireguard_sent_bytes_total counter
|
||||||
|
|
||||||
|
# HELP wireguard_received_bytes_total Bytes received from the peer
|
||||||
|
# TYPE wireguard_received_bytes_total counter
|
||||||
|
|
||||||
|
# HELP wireguard_latest_handshake_seconds Seconds from the last handshake
|
||||||
|
# TYPE wireguard_latest_handshake_seconds gauge
|
||||||
|
```
|
||||||
|
|
||||||
|
## Display some Metrics
|
||||||
|
|
||||||
|
If your laptop is a Kilo peer of the cluster you can access the Prometheus UI by navigating your browser directly to the cluster IP of the `prometheus-k8s` service.
|
||||||
|
Otherwise use `port-forward`:
|
||||||
|
```shell
|
||||||
|
kubectl -n monitoring port-forward svc/prometheus-k8s 9090
|
||||||
|
```
|
||||||
|
and navigate your browser to `localhost:9090`.
|
||||||
|
Check if you can see the PodMonitors for Kilo and the WireGuard Exporter under **Status** -> **Targets** in the Prometheus web UI.
|
||||||
|
|
||||||
|
If you don't see them, check the logs of the `prometheus-k8s` Pods; it may be that Prometheus doesn't have the permission to get Pods in the `kilo` namespace.
|
||||||
|
In this case, you need to apply the Role and RoleBinding from above.
|
||||||
|
|
||||||
|
Navigate to **Graph** and try to execute a simple query, e.g. type `kilo_nodes` and click on `execute`.
|
||||||
|
You should see some data.
|
||||||
|
|
||||||
|
## Using Grafana
|
||||||
|
|
||||||
|
Let's navigate to the Grafana dashboard.
|
||||||
|
Again, if your laptop is not a Kilo peer, use `port-forward`:
|
||||||
|
```shell
|
||||||
|
kubectl -n monitoring port-forward svc/grafana 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
Now navigate your browser to `localhost:3000`.
|
||||||
|
The default user and password is `admin` `admin`.
|
||||||
|
|
||||||
|
An example configuration for a dashboard displaying Kilo metrics can be found [here](https://raw.githubusercontent.com/squat/kilo/main/docs/grafana/kilo.json).
|
||||||
|
You can import this dashboard by hitting **+** -> **Import** on the Grafana dashboard.
|
||||||
|
|
||||||
|
The dashboard looks like this:
|
||||||
|
|
||||||
|
<img src="./graphs/kilo.png" />
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ Support for [Kubernetes network policies](https://kubernetes.io/docs/concepts/se
|
|||||||
The following command adds network policy support by deploying kube-router to work alongside Kilo:
|
The following command adds network policy support by deploying kube-router to work alongside Kilo:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
kubectl apply -f kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kube-router.yaml
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kube-router.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|||||||
22
docs/peer-validation.md
Normal file
22
docs/peer-validation.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Peer Validation
|
||||||
|
|
||||||
|
A [ValidatingAdmissionWebhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook) can be used to avoid applying faulty Peer configurations to the cluster.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
A [ValidatingWebhookConfiguration](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#configure-admission-webhooks-on-the-fly) is a Kubernetes resource that can be used to dynamically specify a service (i.e. the webhook server) that should validate operations (e.g. `UPDATE`, `CREATE`, etc.) on a particular resource (e.g. Kilo Peers).
|
||||||
|
Once such a configuration is applied, the Kubernetes API server will send an AdmissionReviewRequest to the webhook service every time the specified operations are applied to the resource of the specified type.
|
||||||
|
With regard to the [failure policy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy), the API server will apply the requested changes to a resource if the request was answered with `"allowed": true`, or deny the changes if the answer was `"allowed": false`.
|
||||||
|
|
||||||
|
In case of Kilo Peer Validation, the specified operations are `UPDATE` and `CREATE`, the resources are `Peers`, and the default `failurePolicy` is set to `Fail`.
|
||||||
|
View the full ValidatingWebhookConfiguration [here](https://github.com/squat/kilo/blob/main/manifests/peer-validation.yaml).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Apply the Service, the Deployment of the actual webserver, and the ValidatingWebhookConfiguration with:
|
||||||
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/blob/main/manifests/peer-validation.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
The Kubernetes API server will only talk to webhook servers via TLS so the Kilo-Peer-Validation server must be given a valid TLS certificate and key, and the API server must be told what certificate authority (CA) to trust.
|
||||||
|
The above manifest will use [kube-webhook-certgen](https://github.com/jet/kube-webhook-certgen) to generate the requiered certificates and patch the [ValidatingWebhookConfiguration](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#configure-admission-webhooks-on-the-fly).
|
||||||
@@ -6,29 +6,39 @@ This can make sense in cases where
|
|||||||
* not all nodes in a cluster have WireGuard installed; or
|
* not all nodes in a cluster have WireGuard installed; or
|
||||||
* nodes are effectively immutable and kernel modules cannot be installed.
|
* nodes are effectively immutable and kernel modules cannot be installed.
|
||||||
|
|
||||||
|
One example of a userspace implementation of WireGuard is [BoringTun].
|
||||||
|
|
||||||
## Homogeneous Clusters
|
## Homogeneous Clusters
|
||||||
|
|
||||||
In a homogeneous cluster where no node has the WireGuard kernel module, a userspace WireGuard implementation can be made available by deploying a DaemonSet.
|
In a homogeneous cluster where no node has the WireGuard kernel module, a userspace WireGuard implementation can be made available by deploying a DaemonSet.
|
||||||
This DaemonSet creates a WireGuard interface that Kilo will manage.
|
This DaemonSet creates a WireGuard interface that Kilo will manage.
|
||||||
In order to avoid race conditions, `kg` needs to be passed the `--create-interface=false` flag.
|
|
||||||
|
|
||||||
An example configuration for a k3s cluster with [boringtun](https://github.com/cloudflare/boringtun) can be applied with:
|
> **Note**: in order to avoid race conditions, `kg` needs to be passed the `--create-interface=false` flag.
|
||||||
|
|
||||||
|
An example configuration for a K3s cluster with [BoringTun] can be applied with:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/crds.yaml
|
||||||
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-k3s-userspace.yaml
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-k3s-userspace.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
__Note:__ even if some nodes have the WireGuard kernel module, this configuration will cause all nodes to use the userspace implementation of WireGuard.
|
> **Note**: even if some nodes have the WireGuard kernel module, this configuration will cause all nodes to use the userspace implementation of WireGuard.
|
||||||
|
|
||||||
## Heterogeneous Clusters
|
## Heterogeneous Clusters
|
||||||
|
|
||||||
In a heterogeneous cluster where some nodes are missing the WireGuard kernel module, a userspace WireGuard implementation can be provided only to the nodes that need it while enabling the other nodes to leverage WireGuard via the kernel module.
|
In a heterogeneous cluster where some nodes are missing the WireGuard kernel module, a userspace WireGuard implementation can be provided only to the nodes that need it while enabling the other nodes to leverage WireGuard via the kernel module.
|
||||||
An example of such a configuration for a k3s cluster can by applied with:
|
An example of such a configuration for a K3s cluster can by applied with:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/crds.yaml
|
||||||
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-k3s-userspace-heterogeneous.yaml
|
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-k3s-userspace-heterogeneous.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
This configuration will deploy [nkml](https://github.com/leonnicolas/nkml) as a DaemonSet to label all nodes according to the presence of the WireGuard kernel module.
|
This configuration will deploy [nkml](https://github.com/leonnicolas/nkml) as a DaemonSet to label all nodes according to the presence of the WireGuard kernel module.
|
||||||
It will also create two different DaemonSets with Kilo: `kilo` without userspace WireGuard and `kilo-userspace` with boringtun as a sidecar.
|
It will also create two different DaemonSets with Kilo:
|
||||||
__Note:__ because Kilo is dependant on nkml, nkml must be run on the host network before CNI is available and requires a kubeconfig in order to access the Kubernetes API.
|
1. `kilo` without userspace WireGuard; and
|
||||||
|
1. `kilo-userspace` with [BoringTun] as a sidecar.
|
||||||
|
|
||||||
|
> **Note**: because Kilo is dependant on nkml, nkml must be run on the host network before CNI is available and requires a kubeconfig in order to access the Kubernetes API.
|
||||||
|
|
||||||
|
[BoringTun]: https://github.com/cloudflare/boringtun
|
||||||
|
|||||||
43
e2e/full-mesh.sh
Normal file
43
e2e/full-mesh.sh
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. lib.sh
|
||||||
|
|
||||||
|
setup_suite() {
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
_kubectl patch ds -n kube-system kilo -p '{"spec": {"template":{"spec":{"containers":[{"name":"kilo","args":["--hostname=$(NODE_NAME)","--create-interface=false","--kubeconfig=/etc/kubernetes/kubeconfig","--mesh-granularity=full"]}]}}}}'
|
||||||
|
block_until_ready_by_name kube-system kilo-userspace
|
||||||
|
_kubectl wait pod -l app.kubernetes.io/name=adjacency --for=condition=Ready --timeout 3m
|
||||||
|
}
|
||||||
|
|
||||||
|
test_full_mesh_connectivity() {
|
||||||
|
assert "retry 30 5 '' check_ping" "should be able to ping all Pods"
|
||||||
|
assert "retry 10 5 'the adjacency matrix is not complete yet' check_adjacent 3" "adjacency should return the right number of successful pings"
|
||||||
|
echo "sleep for 30s (one reconciliation period) and try again..."
|
||||||
|
sleep 30
|
||||||
|
assert "retry 10 5 'the adjacency matrix is not complete yet' check_adjacent 3" "adjacency should return the right number of successful pings after reconciling"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_full_mesh_peer() {
|
||||||
|
check_peer wg99 e2e 10.5.0.1/32 full
|
||||||
|
}
|
||||||
|
|
||||||
|
test_full_mesh_allowed_location_ips() {
|
||||||
|
docker exec kind-cluster-kilo-control-plane ip address add 10.6.0.1/32 dev eth0
|
||||||
|
_kubectl annotate node kind-cluster-kilo-control-plane kilo.squat.ai/allowed-location-ips=10.6.0.1/32
|
||||||
|
assert_equals Unauthorized "$(retry 10 5 'IP is not yet routable' curl_pod -m 1 -s -k https://10.6.0.1:10250/healthz)" "should be able to make HTTP request to allowed location IP"
|
||||||
|
_kubectl annotate node kind-cluster-kilo-control-plane kilo.squat.ai/allowed-location-ips-
|
||||||
|
assert "retry 10 5 'IP is still routable' _not curl_pod -m 1 -s -k https://10.6.0.1:10250/healthz" "should not be able to make HTTP request to allowed location IP"
|
||||||
|
docker exec kind-cluster-kilo-control-plane ip address delete 10.6.0.1/32 dev eth0
|
||||||
|
}
|
||||||
|
|
||||||
|
test_reject_peer_empty_allowed_ips() {
|
||||||
|
assert_fail "create_peer e2e '' 0 foo" "should not be able to create Peer with empty allowed IPs"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_reject_peer_empty_public_key() {
|
||||||
|
assert_fail "create_peer e2e 10.5.0.1/32 0 ''" "should not be able to create Peer with empty public key"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_mesh_granularity_auto_detect() {
|
||||||
|
assert_equals "$(_kgctl graph)" "$(_kgctl graph --mesh-granularity full)"
|
||||||
|
}
|
||||||
26
e2e/handlers.sh
Normal file
26
e2e/handlers.sh
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. lib.sh
|
||||||
|
|
||||||
|
setup_suite() {
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
block_until_ready_by_name kube-system kilo-userspace
|
||||||
|
_kubectl wait pod -l app.kubernetes.io/name=adjacency --for=condition=Ready --timeout 3m
|
||||||
|
}
|
||||||
|
|
||||||
|
test_graph_handler() {
|
||||||
|
assert "curl_pod 'http://10.4.0.1:1107/graph?format=svg&layout=circo' | grep -q '<svg'" "graph handler should produce SVG output"
|
||||||
|
assert "curl_pod http://10.4.0.1:1107/graph?layout=circo | grep -q '<svg'" "graph handler should default to SVG output"
|
||||||
|
assert "curl_pod http://10.4.0.1:1107/graph | grep -q '<svg'" "graph handler should default to SVG output"
|
||||||
|
assert_fail "curl_pod http://10.4.0.1:1107/graph?layout=fake | grep -q '<svg'" "graph handler should reject invalid layout"
|
||||||
|
assert_fail "curl_pod http://10.4.0.1:1107/graph?format=fake | grep -q '<svg'" "graph handler should reject invalid format"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_health_handler() {
|
||||||
|
assert "curl_pod http://10.4.0.1:1107/health" "health handler should return a status code of 200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_metrics_handler() {
|
||||||
|
assert "curl_pod http://10.4.0.1:1107/metrics" "metrics handler should return a status code of 200"
|
||||||
|
assert "(( $(curl_pod http://10.4.0.1:1107/metrics | grep -E ^kilo_nodes | cut -d " " -f 2) > 0 ))" "metrics handler should provide metric: kilo_nodes > 0"
|
||||||
|
}
|
||||||
25
e2e/helper-curl.yaml
Normal file
25
e2e/helper-curl.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: curl
|
||||||
|
name: curl
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: curl
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: curl
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- while [ 1 -eq 1 ] ; do sleep 10; done
|
||||||
|
image: curlimages/curl
|
||||||
|
name: curl
|
||||||
|
restartPolicy: Always
|
||||||
17
e2e/kgctl.sh
Normal file
17
e2e/kgctl.sh
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. lib.sh
|
||||||
|
|
||||||
|
setup_suite() {
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
block_until_ready_by_name kube-system kilo-userspace
|
||||||
|
_kubectl wait pod -l app.kubernetes.io/name=adjacency --for=condition=Ready --timeout 3m
|
||||||
|
}
|
||||||
|
|
||||||
|
test_connect() {
|
||||||
|
local PEER=test
|
||||||
|
local ALLOWED_IP=10.5.0.1/32
|
||||||
|
docker run -d --name="$PEER" --rm --network=host --cap-add=NET_ADMIN -v "$KGCTL_BINARY":/kgctl -v "$PWD/$KUBECONFIG":/kubeconfig --entrypoint=/kgctl alpine --kubeconfig /kubeconfig connect "$PEER" --allowed-ip "$ALLOWED_IP"
|
||||||
|
assert "retry 10 5 '' check_ping --local" "should be able to ping Pods from host"
|
||||||
|
docker stop "$PEER"
|
||||||
|
}
|
||||||
203
e2e/kilo-kind-userspace.yaml
Normal file
203
e2e/kilo-kind-userspace.yaml
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
data:
|
||||||
|
cni-conf.json: |
|
||||||
|
{
|
||||||
|
"cniVersion":"0.4.0",
|
||||||
|
"name":"kilo",
|
||||||
|
"plugins":[
|
||||||
|
{
|
||||||
|
"name":"kubernetes",
|
||||||
|
"type":"bridge",
|
||||||
|
"bridge":"kube-bridge",
|
||||||
|
"isDefaultGateway":true,
|
||||||
|
"forceAddress":true,
|
||||||
|
"mtu": 1420,
|
||||||
|
"ipam":{
|
||||||
|
"type":"host-local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type":"portmap",
|
||||||
|
"snat":true,
|
||||||
|
"capabilities":{
|
||||||
|
"portMappings":true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- kilo.squat.ai
|
||||||
|
resources:
|
||||||
|
- peers
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- apiextensions.k8s.io
|
||||||
|
resources:
|
||||||
|
- customresourcedefinitions
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: kilo
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: kilo-userspace
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: kilo-userspace
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: kilo-userspace
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
spec:
|
||||||
|
serviceAccountName: kilo
|
||||||
|
hostNetwork: true
|
||||||
|
containers:
|
||||||
|
- name: kilo
|
||||||
|
image: squat/kilo:test
|
||||||
|
imagePullPolicy: Never
|
||||||
|
args:
|
||||||
|
- --hostname=$(NODE_NAME)
|
||||||
|
- --create-interface=false
|
||||||
|
- --mesh-granularity=full
|
||||||
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
|
env:
|
||||||
|
- name: NODE_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: cni-conf-dir
|
||||||
|
mountPath: /etc/cni/net.d
|
||||||
|
- name: kilo-dir
|
||||||
|
mountPath: /var/lib/kilo
|
||||||
|
- name: lib-modules
|
||||||
|
mountPath: /lib/modules
|
||||||
|
readOnly: true
|
||||||
|
- name: xtables-lock
|
||||||
|
mountPath: /run/xtables.lock
|
||||||
|
readOnly: false
|
||||||
|
- name: wireguard
|
||||||
|
mountPath: /var/run/wireguard
|
||||||
|
readOnly: false
|
||||||
|
- name: kubeconfig
|
||||||
|
mountPath: /etc/kubernetes
|
||||||
|
readOnly: true
|
||||||
|
- name: boringtun
|
||||||
|
image: leonnicolas/boringtun
|
||||||
|
args:
|
||||||
|
- --disable-drop-privileges
|
||||||
|
- --foreground
|
||||||
|
- kilo0
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: wireguard
|
||||||
|
mountPath: /var/run/wireguard
|
||||||
|
readOnly: false
|
||||||
|
initContainers:
|
||||||
|
- name: install-cni
|
||||||
|
image: squat/kilo:test
|
||||||
|
imagePullPolicy: Never
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- set -e -x;
|
||||||
|
cp /opt/cni/bin/* /host/opt/cni/bin/;
|
||||||
|
TMP_CONF="$CNI_CONF_NAME".tmp;
|
||||||
|
echo "$CNI_NETWORK_CONFIG" > $TMP_CONF;
|
||||||
|
rm -f /host/etc/cni/net.d/*;
|
||||||
|
mv $TMP_CONF /host/etc/cni/net.d/$CNI_CONF_NAME
|
||||||
|
env:
|
||||||
|
- name: CNI_CONF_NAME
|
||||||
|
value: 10-kilo.conflist
|
||||||
|
- name: CNI_NETWORK_CONFIG
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: kilo
|
||||||
|
key: cni-conf.json
|
||||||
|
volumeMounts:
|
||||||
|
- name: cni-bin-dir
|
||||||
|
mountPath: /host/opt/cni/bin
|
||||||
|
- name: cni-conf-dir
|
||||||
|
mountPath: /host/etc/cni/net.d
|
||||||
|
tolerations:
|
||||||
|
- effect: NoSchedule
|
||||||
|
operator: Exists
|
||||||
|
- effect: NoExecute
|
||||||
|
operator: Exists
|
||||||
|
volumes:
|
||||||
|
- name: cni-bin-dir
|
||||||
|
hostPath:
|
||||||
|
path: /opt/cni/bin
|
||||||
|
- name: cni-conf-dir
|
||||||
|
hostPath:
|
||||||
|
path: /etc/cni/net.d
|
||||||
|
- name: kilo-dir
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/kilo
|
||||||
|
- name: lib-modules
|
||||||
|
hostPath:
|
||||||
|
path: /lib/modules
|
||||||
|
- name: xtables-lock
|
||||||
|
hostPath:
|
||||||
|
path: /run/xtables.lock
|
||||||
|
type: FileOrCreate
|
||||||
|
- name: wireguard
|
||||||
|
hostPath:
|
||||||
|
path: /var/run/wireguard
|
||||||
|
- name: kubeconfig
|
||||||
|
secret:
|
||||||
|
secretName: kubeconfig
|
||||||
10
e2e/kind-config.yaml
Normal file
10
e2e/kind-config.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
kind: Cluster
|
||||||
|
apiVersion: kind.x-k8s.io/v1alpha4
|
||||||
|
nodes:
|
||||||
|
- role: control-plane$WORKERS
|
||||||
|
networking:
|
||||||
|
disableDefaultCNI: true # disable kindnet
|
||||||
|
apiServerAddress: 172.18.0.1
|
||||||
|
apiServerPort: $API_SERVER_PORT
|
||||||
|
podSubnet: $POD_SUBNET
|
||||||
|
serviceSubnet: $SERVICE_SUBNET
|
||||||
200
e2e/lib.sh
Executable file
200
e2e/lib.sh
Executable file
@@ -0,0 +1,200 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
KUBECONFIG="kind.yaml"
|
||||||
|
KIND_CLUSTER="kind-cluster-kilo"
|
||||||
|
KIND_BINARY="${KIND_BINARY:-kind}"
|
||||||
|
KUBECTL_BINARY="${KUBECTL_BINARY:-kubectl}"
|
||||||
|
KGCTL_BINARY="${KGCTL_BINARY:-kgctl}"
|
||||||
|
KILO_IMAGE="${KILO_IMAGE:-squat/kilo}"
|
||||||
|
|
||||||
|
retry() {
|
||||||
|
local COUNT="${1:-10}"
|
||||||
|
local SLEEP="${2:-5}"
|
||||||
|
local ERROR=$3
|
||||||
|
[ -n "$ERROR" ] && ERROR="$ERROR "
|
||||||
|
shift 3
|
||||||
|
for c in $(seq 1 "$COUNT"); do
|
||||||
|
if "$@"; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
printf "%s(attempt %d/%d)\n" "$ERROR" "$c" "$COUNT" | color "$YELLOW" 1>&2
|
||||||
|
if [ "$c" != "$COUNT" ]; then
|
||||||
|
printf "retrying in %d seconds...\n" "$SLEEP" | color "$YELLOW" 1>&2
|
||||||
|
sleep "$SLEEP"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_not() {
|
||||||
|
if "$@"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# _kubectl is a helper that calls kubectl with the --kubeconfig flag.
|
||||||
|
_kubectl() {
|
||||||
|
$KUBECTL_BINARY --kubeconfig="$KUBECONFIG" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# _kgctl is a helper that calls kgctl with the --kubeconfig flag.
|
||||||
|
_kgctl() {
|
||||||
|
$KGCTL_BINARY --kubeconfig="$KUBECONFIG" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# _kind is a helper that calls kind with the --kubeconfig flag.
|
||||||
|
_kind() {
|
||||||
|
$KIND_BINARY --kubeconfig="$KUBECONFIG" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2120
|
||||||
|
build_kind_config() {
|
||||||
|
local WORKER_COUNT="${1:-0}"
|
||||||
|
export API_SERVER_PORT="${2:-6443}"
|
||||||
|
export POD_SUBNET="${3:-10.42.0.0/16}"
|
||||||
|
export SERVICE_SUBNET="${4:-10.43.0.0/16}"
|
||||||
|
export WORKERS=""
|
||||||
|
local i=0
|
||||||
|
while [ "$i" -lt "$WORKER_COUNT" ]; do
|
||||||
|
WORKERS="$(printf "%s\n- role: worker" "$WORKERS")"
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
envsubst < ./kind-config.yaml
|
||||||
|
unset API_SERVER_PORT POD_SUBNET SERVICE_SUBNET WORKERS
|
||||||
|
}
|
||||||
|
|
||||||
|
create_interface() {
|
||||||
|
docker run -d --name="$1" --rm --network=host --cap-add=NET_ADMIN --device=/dev/net/tun -v /var/run/wireguard:/var/run/wireguard -e WG_LOG_LEVEL=debug leonnicolas/boringtun --foreground --disable-drop-privileges "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_interface() {
|
||||||
|
docker rm --force "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_peer() {
|
||||||
|
cat <<EOF | _kubectl apply -f -
|
||||||
|
apiVersion: kilo.squat.ai/v1alpha1
|
||||||
|
kind: Peer
|
||||||
|
metadata:
|
||||||
|
name: $1
|
||||||
|
spec:
|
||||||
|
allowedIPs:
|
||||||
|
- $2
|
||||||
|
persistentKeepalive: $3
|
||||||
|
publicKey: $4
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_peer() {
|
||||||
|
_kubectl delete peer "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_ready() {
|
||||||
|
for pod in $(_kubectl -n "$1" get pods -o name -l "$2"); do
|
||||||
|
if ! _kubectl -n "$1" get "$pod" | tail -n 1 | grep -q Running; then
|
||||||
|
return 1;
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Returns non zero if one pod of the given name in the given namespace is not ready.
|
||||||
|
block_until_ready_by_name() {
|
||||||
|
block_until_ready "$1" "app.kubernetes.io/name=$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Blocks until all pods of a deployment are ready.
|
||||||
|
block_until_ready() {
|
||||||
|
retry 30 5 "some $2 pods are not ready yet" is_ready "$1" "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# create_cluster launches a kind cluster and deploys Kilo, Adjacency, and a helper with curl.
|
||||||
|
create_cluster() {
|
||||||
|
# shellcheck disable=SC2119
|
||||||
|
local CONFIG="${1:-$(build_kind_config)}"
|
||||||
|
_kind delete clusters $KIND_CLUSTER > /dev/null
|
||||||
|
# Create the kind cluster.
|
||||||
|
_kind create cluster --name $KIND_CLUSTER --config <(echo "$CONFIG")
|
||||||
|
# Load the Kilo image into kind.
|
||||||
|
docker tag "$KILO_IMAGE" squat/kilo:test
|
||||||
|
# This command does not accept the --kubeconfig flag, so call the command directly.
|
||||||
|
$KIND_BINARY load docker-image squat/kilo:test --name $KIND_CLUSTER
|
||||||
|
# Create the kubeconfig secret.
|
||||||
|
_kubectl create secret generic kubeconfig --from-file=kubeconfig="$KUBECONFIG" -n kube-system
|
||||||
|
# Apply Kilo the the cluster.
|
||||||
|
_kubectl apply -f ../manifests/crds.yaml
|
||||||
|
_kubectl apply -f kilo-kind-userspace.yaml
|
||||||
|
block_until_ready_by_name kube-system kilo-userspace
|
||||||
|
_kubectl wait nodes --all --for=condition=Ready
|
||||||
|
# Wait for CoreDNS.
|
||||||
|
block_until_ready kube_system k8s-app=kube-dns
|
||||||
|
# Ensure the curl helper is not scheduled on a control-plane node.
|
||||||
|
_kubectl apply -f helper-curl.yaml
|
||||||
|
block_until_ready_by_name default curl
|
||||||
|
_kubectl taint node $KIND_CLUSTER-control-plane node-role.kubernetes.io/master:NoSchedule-
|
||||||
|
_kubectl apply -f https://raw.githubusercontent.com/kilo-io/adjacency/main/example.yaml
|
||||||
|
block_until_ready_by_name default adjacency
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_cluster () {
|
||||||
|
_kind delete clusters $KIND_CLUSTER
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_pod() {
|
||||||
|
_kubectl get pods -l app.kubernetes.io/name=curl -o name | xargs -I{} "$KUBECTL_BINARY" --kubeconfig="$KUBECONFIG" exec {} -- /usr/bin/curl "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_ping() {
|
||||||
|
local LOCAL
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case $1 in
|
||||||
|
--local)
|
||||||
|
LOCAL=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
for ip in $(_kubectl get pods -l app.kubernetes.io/name=adjacency -o jsonpath='{.items[*].status.podIP}'); do
|
||||||
|
if [ -n "$LOCAL" ]; then
|
||||||
|
ping=$(curl -m 1 -s http://"$ip":8080/ping)
|
||||||
|
else
|
||||||
|
ping=$(curl_pod -m 1 -s http://"$ip":8080/ping)
|
||||||
|
fi
|
||||||
|
if [ "$ping" = "pong" ]; then
|
||||||
|
echo "successfully pinged $ip"
|
||||||
|
else
|
||||||
|
printf 'failed to ping %s; expected "pong" but got "%s"\n' "$ip" "$ping"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_adjacent() {
|
||||||
|
curl_pod adjacency:8080/?format=fancy
|
||||||
|
[ "$(curl_pod -m 1 -s adjacency:8080/?format=json | jq '.[].latencies[].ok' | grep -c true)" -eq $(($1*$1)) ]
|
||||||
|
}
|
||||||
|
|
||||||
|
check_peer() {
|
||||||
|
local INTERFACE=$1
|
||||||
|
local PEER=$2
|
||||||
|
local ALLOWED_IP=$3
|
||||||
|
local GRANULARITY=$4
|
||||||
|
create_interface "$INTERFACE"
|
||||||
|
docker run --rm leonnicolas/wg-tools wg genkey > "$INTERFACE"
|
||||||
|
assert "create_peer $PEER $ALLOWED_IP 10 $(docker run --rm --entrypoint=/bin/sh -v "$PWD/$INTERFACE":/key leonnicolas/wg-tools -c 'cat /key | wg pubkey')" "should be able to create Peer"
|
||||||
|
assert "_kgctl showconf peer $PEER --mesh-granularity=$GRANULARITY > $PEER.ini" "should be able to get Peer configuration"
|
||||||
|
assert "docker run --rm --network=host --cap-add=NET_ADMIN --entrypoint=/usr/bin/wg -v /var/run/wireguard:/var/run/wireguard -v $PWD/$PEER.ini:/peer.ini leonnicolas/wg-tools setconf $INTERFACE /peer.ini" "should be able to apply configuration from kgctl"
|
||||||
|
docker run --rm --network=host --cap-add=NET_ADMIN --entrypoint=/usr/bin/wg -v /var/run/wireguard:/var/run/wireguard -v "$PWD/$INTERFACE":/key leonnicolas/wg-tools set "$INTERFACE" private-key /key
|
||||||
|
docker run --rm --network=host --cap-add=NET_ADMIN --entrypoint=/sbin/ip leonnicolas/wg-tools address add "$ALLOWED_IP" dev "$INTERFACE"
|
||||||
|
docker run --rm --network=host --cap-add=NET_ADMIN --entrypoint=/sbin/ip leonnicolas/wg-tools link set "$INTERFACE" up
|
||||||
|
docker run --rm --network=host --cap-add=NET_ADMIN --entrypoint=/sbin/ip leonnicolas/wg-tools route add 10.42/16 dev "$INTERFACE"
|
||||||
|
assert "retry 10 5 '' check_ping --local" "should be able to ping Pods from host"
|
||||||
|
assert_equals "$(_kgctl showconf peer "$PEER")" "$(_kgctl showconf peer "$PEER" --mesh-granularity="$GRANULARITY")" "kgctl should be able to auto detect the mesh granularity"
|
||||||
|
rm "$INTERFACE" "$PEER".ini
|
||||||
|
delete_peer "$PEER"
|
||||||
|
delete_interface "$INTERFACE"
|
||||||
|
}
|
||||||
26
e2e/location-mesh.sh
Executable file
26
e2e/location-mesh.sh
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. lib.sh
|
||||||
|
|
||||||
|
setup_suite() {
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
_kubectl patch ds -n kube-system kilo -p '{"spec": {"template":{"spec":{"containers":[{"name":"kilo","args":["--hostname=$(NODE_NAME)","--create-interface=false","--kubeconfig=/etc/kubernetes/kubeconfig","--mesh-granularity=location"]}]}}}}'
|
||||||
|
block_until_ready_by_name kube-system kilo-userspace
|
||||||
|
_kubectl wait pod -l app.kubernetes.io/name=adjacency --for=condition=Ready --timeout 3m
|
||||||
|
}
|
||||||
|
|
||||||
|
test_location_mesh_connectivity() {
|
||||||
|
assert "retry 30 5 '' check_ping" "should be able to ping all Pods"
|
||||||
|
assert "retry 10 5 'the adjacency matrix is not complete yet' check_adjacent 3" "adjacency should return the right number of successful pings"
|
||||||
|
echo "sleep for 30s (one reconciliation period) and try again..."
|
||||||
|
sleep 30
|
||||||
|
assert "retry 10 5 'the adjacency matrix is not complete yet' check_adjacent 3" "adjacency should return the right number of successful pings after reconciling"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_location_mesh_peer() {
|
||||||
|
check_peer wg99 e2e 10.5.0.1/32 location
|
||||||
|
}
|
||||||
|
|
||||||
|
test_mesh_granularity_auto_detect() {
|
||||||
|
assert_equals "$(_kgctl graph)" "$(_kgctl graph --mesh-granularity location)"
|
||||||
|
}
|
||||||
66
e2e/multi-cluster.sh
Executable file
66
e2e/multi-cluster.sh
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. lib.sh
|
||||||
|
|
||||||
|
# shellcheck disable=SC2153
|
||||||
|
KUBECONFIG2="$KUBECONFIG"2
|
||||||
|
# shellcheck disable=SC2153
|
||||||
|
KIND_CLUSTER2="$KIND_CLUSTER"2
|
||||||
|
|
||||||
|
setup_suite() {
|
||||||
|
KUBECONFIG=$KUBECONFIG2 KIND_CLUSTER=$KIND_CLUSTER2 create_cluster "$(build_kind_config 1 6444 10.44.0.0/16 10.45.0.0/16)"
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
KUBECONFIG=$KUBECONFIG2 _kubectl patch ds -n kube-system kilo -p '{"spec": {"template":{"spec":{"containers":[{"name":"kilo","args":["--hostname=$(NODE_NAME)","--create-interface=false","--kubeconfig=/etc/kubernetes/kubeconfig","--mesh-granularity=full","--subnet=10.6.0.0/16"]}]}}}}'
|
||||||
|
KUBECONFIG=$KUBECONFIG2 block_until_ready_by_name kube-system kilo-userspace
|
||||||
|
# Register the nodes in cluster1 as peers of cluster2.
|
||||||
|
for n in $(_kubectl get no -o name | cut -d'/' -f2); do
|
||||||
|
# Specify the service CIDR as an extra IP range that should be routable.
|
||||||
|
$KGCTL_BINARY --kubeconfig "$KUBECONFIG" showconf node "$n" --as-peer -o yaml --allowed-ips 10.43.0.0/16 | $KUBECTL_BINARY --kubeconfig "$KUBECONFIG2" apply -f -
|
||||||
|
done
|
||||||
|
# Register the nodes in cluster2 as peers of cluster1.
|
||||||
|
for n in $(KUBECONFIG=$KUBECONFIG2 _kubectl get no -o name | cut -d'/' -f2); do
|
||||||
|
# Specify the service CIDR as an extra IP range that should be routable.
|
||||||
|
$KGCTL_BINARY --kubeconfig "$KUBECONFIG2" showconf node "$n" --as-peer -o yaml --allowed-ips 10.45.0.0/16 | $KUBECTL_BINARY --kubeconfig "$KUBECONFIG" apply -f -
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
test_multi_cluster_pod_connectivity() {
|
||||||
|
for ip in $(KUBECONFIG=$KUBECONFIG2 _kubectl get pods -l app.kubernetes.io/name=adjacency -o jsonpath='{.items[*].status.podIP}'); do
|
||||||
|
assert_equals pong "$(retry 10 5 "$ip is not yet routable" curl_pod -m 1 -s http://"$ip":8080/ping)" "should be able to make HTTP request from cluster 1 to Pod in cluster 2"
|
||||||
|
done
|
||||||
|
for ip in $(_kubectl get pods -l app.kubernetes.io/name=adjacency -o jsonpath='{.items[*].status.podIP}'); do
|
||||||
|
assert_equals pong "$(KUBECONFIG="$KUBECONFIG2" retry 10 5 "$ip is not yet routable" curl_pod -m 1 -s http://"$ip":8080/ping)" "should be able to make HTTP request from cluster 2 to Pod in cluster 1"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
test_multi_cluster_service_connectivity() {
|
||||||
|
# Mirror the Kubernetes API service from cluster1 into cluster2.
|
||||||
|
cat <<EOF | $KUBECTL_BINARY --kubeconfig "$KUBECONFIG2" apply -f -
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: mirrored-kubernetes
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Endpoints
|
||||||
|
metadata:
|
||||||
|
name: mirrored-kubernetes
|
||||||
|
subsets:
|
||||||
|
- addresses:
|
||||||
|
- ip: $(_kubectl get service kubernetes -o jsonpath='{.spec.clusterIP}') # The cluster IP of the Kubernetes API service on cluster1.
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
EOF
|
||||||
|
assert_equals ok "$(KUBECONFIG="$KUBECONFIG2" retry 10 5 "service is not yet routable" curl_pod -m 1 -s -k https://mirrored-kubernetes/readyz)" "should be able to make HTTP request from cluster 2 to service in cluster 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown_suite () {
|
||||||
|
# Remove the nodes in cluster2 as peers of cluster1.
|
||||||
|
for n in $(KUBECONFIG=$KUBECONFIG2 _kubectl get no -o name | cut -d'/' -f2); do
|
||||||
|
_kubectl delete peer "$n"
|
||||||
|
done
|
||||||
|
KUBECONFIG=$KUBECONFIG2 KIND_CLUSTER=$KIND_CLUSTER2 delete_cluster
|
||||||
|
}
|
||||||
7
e2e/setup.sh
Normal file
7
e2e/setup.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. lib.sh
|
||||||
|
|
||||||
|
setup_suite() {
|
||||||
|
create_cluster "$(build_kind_config 2)"
|
||||||
|
}
|
||||||
7
e2e/teardown.sh
Normal file
7
e2e/teardown.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. lib.sh
|
||||||
|
|
||||||
|
teardown_suite () {
|
||||||
|
delete_cluster
|
||||||
|
}
|
||||||
138
go.mod
138
go.mod
@@ -1,66 +1,88 @@
|
|||||||
module github.com/squat/kilo
|
module github.com/squat/kilo
|
||||||
|
|
||||||
go 1.15
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
|
||||||
github.com/ant31/crd-validation v0.0.0-20180801212718-38f6a293f140
|
|
||||||
github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310
|
github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310
|
||||||
github.com/campoy/embedmd v1.0.0
|
github.com/campoy/embedmd v1.0.0
|
||||||
github.com/containernetworking/cni v0.6.0
|
github.com/containernetworking/cni v1.0.1
|
||||||
github.com/containernetworking/plugins v0.6.0
|
github.com/containernetworking/plugins v1.1.1
|
||||||
github.com/coreos/go-iptables v0.4.0
|
github.com/coreos/go-iptables v0.6.0
|
||||||
github.com/emicklei/go-restful v2.9.3+incompatible // indirect
|
github.com/go-kit/kit v0.9.0
|
||||||
github.com/evanphx/json-patch v4.2.0+incompatible // indirect
|
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
|
||||||
github.com/go-kit/kit v0.8.0
|
|
||||||
github.com/go-logfmt/logfmt v0.4.0 // indirect
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.0 // indirect
|
|
||||||
github.com/go-openapi/jsonreference v0.19.0 // indirect
|
|
||||||
github.com/go-openapi/spec v0.19.0
|
|
||||||
github.com/go-openapi/swag v0.19.0 // indirect
|
|
||||||
github.com/go-stack/stack v1.8.0 // indirect
|
|
||||||
github.com/gogo/protobuf v1.2.1 // indirect
|
|
||||||
github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff // indirect
|
|
||||||
github.com/golang/protobuf v1.3.1 // indirect
|
|
||||||
github.com/google/gofuzz v1.0.0 // indirect
|
|
||||||
github.com/googleapis/gnostic v0.2.0 // indirect
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
|
||||||
github.com/imdario/mergo v0.3.6 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.6 // indirect
|
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
|
||||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
|
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
|
||||||
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983 // indirect
|
github.com/metalmatze/signal v0.0.0-20210307161603-1c9aa721a97a
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/oklog/run v1.1.0
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
github.com/prometheus/client_golang v1.11.0
|
||||||
github.com/oklog/run v1.0.0
|
github.com/spf13/cobra v1.2.1
|
||||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5
|
||||||
github.com/onsi/gomega v1.5.0 // indirect
|
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881
|
||||||
github.com/prometheus/client_golang v0.9.2
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211124212657-dd7407c86d22
|
||||||
github.com/spf13/cobra v0.0.4-0.20190321000552-67fc4837d267
|
honnef.co/go/tools v0.3.1
|
||||||
github.com/spf13/pflag v1.0.3
|
k8s.io/api v0.23.6
|
||||||
github.com/stretchr/testify v1.3.0 // indirect
|
k8s.io/apiextensions-apiserver v0.23.6
|
||||||
github.com/vishvananda/netlink v1.0.0
|
k8s.io/apimachinery v0.23.6
|
||||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect
|
k8s.io/client-go v0.23.6
|
||||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 // indirect
|
k8s.io/code-generator v0.23.6
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422
|
sigs.k8s.io/controller-tools v0.8.0
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 // indirect
|
)
|
||||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872
|
require (
|
||||||
golang.org/x/text v0.3.2 // indirect
|
github.com/BurntSushi/toml v0.4.1 // indirect
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 // indirect
|
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||||
google.golang.org/appengine v1.5.0 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
github.com/fatih/color v1.12.0 // indirect
|
||||||
k8s.io/api v0.0.0-20190313235455-40a48860b5ab
|
github.com/go-logfmt/logfmt v0.5.0 // indirect
|
||||||
k8s.io/apiextensions-apiserver v0.0.0-20190315093550-53c4693659ed
|
github.com/go-logr/logr v1.2.0 // indirect
|
||||||
k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1
|
github.com/gobuffalo/flect v0.2.3 // indirect
|
||||||
k8s.io/client-go v11.0.0+incompatible
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
k8s.io/code-generator v0.0.0-20190311093542-50b561225d70
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
k8s.io/gengo v0.0.0-20181106084056-51747d6e00da // indirect
|
github.com/google/go-cmp v0.5.6 // indirect
|
||||||
k8s.io/klog v0.3.0 // indirect
|
github.com/google/gofuzz v1.1.0 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30
|
github.com/google/uuid v1.2.0 // indirect
|
||||||
k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7 // indirect
|
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||||
sigs.k8s.io/yaml v1.1.0 // indirect
|
github.com/imdario/mergo v0.3.11 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||||
|
github.com/mdlayher/genetlink v1.0.0 // indirect
|
||||||
|
github.com/mdlayher/netlink v1.4.1 // indirect
|
||||||
|
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
|
github.com/prometheus/common v0.28.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.6.0 // indirect
|
||||||
|
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect
|
||||||
|
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
|
||||||
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||||
|
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20211123210315-387f7c461a16 // 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/gengo v0.0.0-20210813121822-485abfe95c7c // 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-20211116205334-6203023598ed // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
93
manifests/crds.yaml
Normal file
93
manifests/crds.yaml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
controller-gen.kubebuilder.io/version: v0.8.0
|
||||||
|
creationTimestamp: null
|
||||||
|
name: peers.kilo.squat.ai
|
||||||
|
spec:
|
||||||
|
group: kilo.squat.ai
|
||||||
|
names:
|
||||||
|
kind: Peer
|
||||||
|
listKind: PeerList
|
||||||
|
plural: peers
|
||||||
|
singular: peer
|
||||||
|
scope: Cluster
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
description: Peer is a WireGuard peer that should have access to the VPN.
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
description: 'APIVersion defines the versioned schema of this representation
|
||||||
|
of an object. Servers should convert recognized schemas to the latest
|
||||||
|
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
description: 'Kind is a string value representing the REST resource this
|
||||||
|
object represents. Servers may infer this from the endpoint the client
|
||||||
|
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||||
|
type: string
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
spec:
|
||||||
|
description: 'Specification of the desired behavior of the Kilo Peer.
|
||||||
|
More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status'
|
||||||
|
properties:
|
||||||
|
allowedIPs:
|
||||||
|
description: AllowedIPs is the list of IP addresses that are allowed
|
||||||
|
for the given peer's tunnel.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
endpoint:
|
||||||
|
description: Endpoint is the initial endpoint for connections to the
|
||||||
|
peer.
|
||||||
|
properties:
|
||||||
|
dnsOrIP:
|
||||||
|
description: DNSOrIP is a DNS name or an IP address.
|
||||||
|
properties:
|
||||||
|
dns:
|
||||||
|
description: DNS must be a valid RFC 1123 subdomain.
|
||||||
|
type: string
|
||||||
|
ip:
|
||||||
|
description: IP must be a valid IP address.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
port:
|
||||||
|
description: Port must be a valid port number.
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- dnsOrIP
|
||||||
|
- port
|
||||||
|
type: object
|
||||||
|
persistentKeepalive:
|
||||||
|
description: PersistentKeepalive is the interval in seconds of the
|
||||||
|
emission of keepalive packets by the peer. This defaults to 0, which
|
||||||
|
disables the feature.
|
||||||
|
type: integer
|
||||||
|
presharedKey:
|
||||||
|
description: PresharedKey is the optional symmetric encryption key
|
||||||
|
for the peer.
|
||||||
|
type: string
|
||||||
|
publicKey:
|
||||||
|
description: PublicKey is the WireGuard public key for the peer.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- allowedIPs
|
||||||
|
- publicKey
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- spec
|
||||||
|
type: object
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
status:
|
||||||
|
acceptedNames:
|
||||||
|
kind: ""
|
||||||
|
plural: ""
|
||||||
|
conditions: []
|
||||||
|
storedVersions: []
|
||||||
@@ -23,14 +23,13 @@ rules:
|
|||||||
- peers
|
- peers
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- update
|
|
||||||
- watch
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- apiextensions.k8s.io
|
- apiextensions.k8s.io
|
||||||
resources:
|
resources:
|
||||||
- customresourcedefinitions
|
- customresourcedefinitions
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- get
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
@@ -52,20 +51,23 @@ metadata:
|
|||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: kilo
|
serviceAccountName: kilo
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
containers:
|
containers:
|
||||||
- name: kilo
|
- name: kilo
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
args:
|
args:
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
@@ -77,6 +79,9 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
cni-conf.json: |
|
cni-conf.json: |
|
||||||
{
|
{
|
||||||
"cniVersion":"0.3.1",
|
"cniVersion":"0.4.0",
|
||||||
"name":"kilo",
|
"name":"kilo",
|
||||||
"plugins":[
|
"plugins":[
|
||||||
{
|
{
|
||||||
@@ -57,14 +57,13 @@ rules:
|
|||||||
- peers
|
- peers
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- update
|
|
||||||
- watch
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- apiextensions.k8s.io
|
- apiextensions.k8s.io
|
||||||
resources:
|
resources:
|
||||||
- customresourcedefinitions
|
- customresourcedefinitions
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- get
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
@@ -86,20 +85,23 @@ metadata:
|
|||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: kilo
|
serviceAccountName: kilo
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
containers:
|
containers:
|
||||||
- name: kilo
|
- name: kilo
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
args:
|
args:
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
@@ -108,6 +110,9 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
@@ -126,7 +131,7 @@ spec:
|
|||||||
readOnly: false
|
readOnly: false
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: install-cni
|
- name: install-cni
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
|
|||||||
@@ -23,14 +23,13 @@ rules:
|
|||||||
- peers
|
- peers
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- update
|
|
||||||
- watch
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- apiextensions.k8s.io
|
- apiextensions.k8s.io
|
||||||
resources:
|
resources:
|
||||||
- customresourcedefinitions
|
- customresourcedefinitions
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- get
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
@@ -45,6 +44,35 @@ subjects:
|
|||||||
name: kilo
|
name: kilo
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
---
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: kilo-scripts
|
||||||
|
namespace: kube-system
|
||||||
|
data:
|
||||||
|
init.sh: |
|
||||||
|
#!/bin/sh
|
||||||
|
cat > /etc/kubernetes/kubeconfig <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Config
|
||||||
|
name: kilo
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
server: $(sed -n 's/.*server: \(.*\)/\1/p' /var/lib/rancher/k3s/agent/kubelet.kubeconfig)
|
||||||
|
certificate-authority: /var/lib/rancher/k3s/agent/server-ca.crt
|
||||||
|
users:
|
||||||
|
- name: kilo
|
||||||
|
user:
|
||||||
|
token: $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
||||||
|
contexts:
|
||||||
|
- name: kilo
|
||||||
|
context:
|
||||||
|
cluster: kilo
|
||||||
|
namespace: ${NAMESPACE}
|
||||||
|
user: kilo
|
||||||
|
current-context: kilo
|
||||||
|
EOF
|
||||||
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: DaemonSet
|
kind: DaemonSet
|
||||||
metadata:
|
metadata:
|
||||||
@@ -52,20 +80,23 @@ metadata:
|
|||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: kilo
|
serviceAccountName: kilo
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
containers:
|
containers:
|
||||||
- name: kilo
|
- name: kilo
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
args:
|
args:
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
@@ -77,13 +108,16 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: kilo-dir
|
- name: kilo-dir
|
||||||
mountPath: /var/lib/kilo
|
mountPath: /var/lib/kilo
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
mountPath: /etc/kubernetes/kubeconfig
|
mountPath: /etc/kubernetes
|
||||||
readOnly: true
|
readOnly: true
|
||||||
- name: lib-modules
|
- name: lib-modules
|
||||||
mountPath: /lib/modules
|
mountPath: /lib/modules
|
||||||
@@ -91,6 +125,28 @@ spec:
|
|||||||
- name: xtables-lock
|
- name: xtables-lock
|
||||||
mountPath: /run/xtables.lock
|
mountPath: /run/xtables.lock
|
||||||
readOnly: false
|
readOnly: false
|
||||||
|
initContainers:
|
||||||
|
- name: generate-kubeconfig
|
||||||
|
image: squat/kilo:0.5.0
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
args:
|
||||||
|
- /scripts/init.sh
|
||||||
|
imagePullPolicy: Always
|
||||||
|
volumeMounts:
|
||||||
|
- name: kubeconfig
|
||||||
|
mountPath: /etc/kubernetes
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts/
|
||||||
|
readOnly: true
|
||||||
|
- name: k3s-agent
|
||||||
|
mountPath: /var/lib/rancher/k3s/agent/
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
tolerations:
|
tolerations:
|
||||||
- effect: NoSchedule
|
- effect: NoSchedule
|
||||||
operator: Exists
|
operator: Exists
|
||||||
@@ -101,11 +157,13 @@ spec:
|
|||||||
hostPath:
|
hostPath:
|
||||||
path: /var/lib/kilo
|
path: /var/lib/kilo
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
hostPath:
|
emptyDir: {}
|
||||||
# Since kilo runs as a daemonset, it is recommended that you copy the
|
- name: scripts
|
||||||
# k3s.yaml kubeconfig file from the master node to all worker nodes
|
configMap:
|
||||||
# with the same path structure.
|
name: kilo-scripts
|
||||||
path: /etc/rancher/k3s/k3s.yaml
|
- name: k3s-agent
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/rancher/k3s/agent
|
||||||
- name: lib-modules
|
- name: lib-modules
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /lib/modules
|
path: /lib/modules
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
cni-conf.json: |
|
cni-conf.json: |
|
||||||
{
|
{
|
||||||
"cniVersion":"0.3.1",
|
"cniVersion":"0.4.0",
|
||||||
"name":"kilo",
|
"name":"kilo",
|
||||||
"plugins":[
|
"plugins":[
|
||||||
{
|
{
|
||||||
@@ -58,14 +58,13 @@ rules:
|
|||||||
- peers
|
- peers
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- update
|
|
||||||
- watch
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- apiextensions.k8s.io
|
- apiextensions.k8s.io
|
||||||
resources:
|
resources:
|
||||||
- customresourcedefinitions
|
- customresourcedefinitions
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- get
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
@@ -80,6 +79,35 @@ subjects:
|
|||||||
name: kilo
|
name: kilo
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
---
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: kilo-scripts
|
||||||
|
namespace: kube-system
|
||||||
|
data:
|
||||||
|
init.sh: |
|
||||||
|
#!/bin/sh
|
||||||
|
cat > /etc/kubernetes/kubeconfig <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Config
|
||||||
|
name: kilo
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
server: $(sed -n 's/.*server: \(.*\)/\1/p' /var/lib/rancher/k3s/agent/kubelet.kubeconfig)
|
||||||
|
certificate-authority: /var/lib/rancher/k3s/agent/server-ca.crt
|
||||||
|
users:
|
||||||
|
- name: kilo
|
||||||
|
user:
|
||||||
|
token: $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
||||||
|
contexts:
|
||||||
|
- name: kilo
|
||||||
|
context:
|
||||||
|
cluster: kilo
|
||||||
|
namespace: ${NAMESPACE}
|
||||||
|
user: kilo
|
||||||
|
current-context: kilo
|
||||||
|
EOF
|
||||||
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: DaemonSet
|
kind: DaemonSet
|
||||||
metadata:
|
metadata:
|
||||||
@@ -87,14 +115,17 @@ metadata:
|
|||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
nkml.squat.ai/wireguard: "true"
|
nkml.squat.ai/wireguard: "true"
|
||||||
@@ -102,7 +133,7 @@ spec:
|
|||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
containers:
|
containers:
|
||||||
- name: kilo
|
- name: kilo
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
args:
|
args:
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
@@ -112,6 +143,9 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
@@ -120,7 +154,7 @@ spec:
|
|||||||
- name: kilo-dir
|
- name: kilo-dir
|
||||||
mountPath: /var/lib/kilo
|
mountPath: /var/lib/kilo
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
mountPath: /etc/kubernetes/kubeconfig
|
mountPath: /etc/kubernetes
|
||||||
readOnly: true
|
readOnly: true
|
||||||
- name: lib-modules
|
- name: lib-modules
|
||||||
mountPath: /lib/modules
|
mountPath: /lib/modules
|
||||||
@@ -129,8 +163,29 @@ spec:
|
|||||||
mountPath: /run/xtables.lock
|
mountPath: /run/xtables.lock
|
||||||
readOnly: false
|
readOnly: false
|
||||||
initContainers:
|
initContainers:
|
||||||
|
- name: generate-kubeconfig
|
||||||
|
image: squat/kilo:0.5.0
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
args:
|
||||||
|
- /scripts/init.sh
|
||||||
|
imagePullPolicy: Always
|
||||||
|
volumeMounts:
|
||||||
|
- name: kubeconfig
|
||||||
|
mountPath: /etc/kubernetes
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts/
|
||||||
|
readOnly: true
|
||||||
|
- name: k3s-agent
|
||||||
|
mountPath: /var/lib/rancher/k3s/agent/
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
- name: install-cni
|
- name: install-cni
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
@@ -169,11 +224,13 @@ spec:
|
|||||||
hostPath:
|
hostPath:
|
||||||
path: /var/lib/kilo
|
path: /var/lib/kilo
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
hostPath:
|
emptyDir: {}
|
||||||
# Since kilo runs as a daemonset, it is recommended that you copy the
|
- name: scripts
|
||||||
# k3s.yaml kubeconfig file from the master node to all worker nodes
|
configMap:
|
||||||
# with the same path structure.
|
name: kilo-scripts
|
||||||
path: /etc/rancher/k3s/k3s.yaml
|
- name: k3s-agent
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/rancher/k3s/agent
|
||||||
- name: lib-modules
|
- name: lib-modules
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /lib/modules
|
path: /lib/modules
|
||||||
@@ -189,14 +246,17 @@ metadata:
|
|||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo-userspace
|
app.kubernetes.io/name: kilo-userspace
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: kilo-userspace
|
app.kubernetes.io/name: kilo-userspace
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo-userspace
|
app.kubernetes.io/name: kilo-userspace
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
nkml.squat.ai/wireguard: "false"
|
nkml.squat.ai/wireguard: "false"
|
||||||
@@ -204,7 +264,7 @@ spec:
|
|||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
containers:
|
containers:
|
||||||
- name: kilo
|
- name: kilo
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
args:
|
args:
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
@@ -215,6 +275,9 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
@@ -223,7 +286,7 @@ spec:
|
|||||||
- name: kilo-dir
|
- name: kilo-dir
|
||||||
mountPath: /var/lib/kilo
|
mountPath: /var/lib/kilo
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
mountPath: /etc/kubernetes/kubeconfig
|
mountPath: /etc/kubernetes
|
||||||
readOnly: true
|
readOnly: true
|
||||||
- name: lib-modules
|
- name: lib-modules
|
||||||
mountPath: /lib/modules
|
mountPath: /lib/modules
|
||||||
@@ -235,9 +298,9 @@ spec:
|
|||||||
mountPath: /var/run/wireguard
|
mountPath: /var/run/wireguard
|
||||||
readOnly: false
|
readOnly: false
|
||||||
- name: boringtun
|
- name: boringtun
|
||||||
image: leonnicolas/boringtun
|
image: leonnicolas/boringtun:cc19859
|
||||||
args:
|
args:
|
||||||
- --disable-drop-privileges=true
|
- --disable-drop-privileges
|
||||||
- --foreground
|
- --foreground
|
||||||
- kilo0
|
- kilo0
|
||||||
securityContext:
|
securityContext:
|
||||||
@@ -247,8 +310,29 @@ spec:
|
|||||||
mountPath: /var/run/wireguard
|
mountPath: /var/run/wireguard
|
||||||
readOnly: false
|
readOnly: false
|
||||||
initContainers:
|
initContainers:
|
||||||
|
- name: generate-kubeconfig
|
||||||
|
image: squat/kilo:0.5.0
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
args:
|
||||||
|
- /scripts/init.sh
|
||||||
|
imagePullPolicy: Always
|
||||||
|
volumeMounts:
|
||||||
|
- name: kubeconfig
|
||||||
|
mountPath: /etc/kubernetes
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts/
|
||||||
|
readOnly: true
|
||||||
|
- name: k3s-agent
|
||||||
|
mountPath: /var/lib/rancher/k3s/agent/
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
- name: install-cni
|
- name: install-cni
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
@@ -287,11 +371,13 @@ spec:
|
|||||||
hostPath:
|
hostPath:
|
||||||
path: /var/lib/kilo
|
path: /var/lib/kilo
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
hostPath:
|
emptyDir: {}
|
||||||
# Since kilo runs as a daemonset, it is recommended that you copy the
|
- name: scripts
|
||||||
# k3s.yaml kubeconfig file from the master node to all worker nodes
|
configMap:
|
||||||
# with the same path structure.
|
name: kilo-scripts
|
||||||
path: /etc/rancher/k3s/k3s.yaml
|
- name: k3s-agent
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/rancher/k3s/agent
|
||||||
- name: lib-modules
|
- name: lib-modules
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /lib/modules
|
path: /lib/modules
|
||||||
@@ -305,7 +391,7 @@ spec:
|
|||||||
---
|
---
|
||||||
kind: DaemonSet
|
kind: DaemonSet
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
metadata:
|
metadata:
|
||||||
name: nkml
|
name: nkml
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
@@ -320,10 +406,11 @@ spec:
|
|||||||
app.kubernetes.io/name: nkml
|
app.kubernetes.io/name: nkml
|
||||||
spec:
|
spec:
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
|
serviceAccountName: kilo
|
||||||
containers:
|
containers:
|
||||||
- name: nkml
|
- name: nkml
|
||||||
image: leonnicolas/nkml
|
image: leonnicolas/nkml
|
||||||
args:
|
args:
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
- --label-mod=wireguard
|
- --label-mod=wireguard
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
@@ -337,13 +424,36 @@ spec:
|
|||||||
containerPort: 8080
|
containerPort: 8080
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
mountPath: /etc/kubernetes/kubeconfig
|
mountPath: /etc/kubernetes
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
initContainers:
|
||||||
|
- name: generate-kubeconfig
|
||||||
|
image: squat/kilo:0.5.0
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
args:
|
||||||
|
- /scripts/init.sh
|
||||||
|
imagePullPolicy: Always
|
||||||
|
volumeMounts:
|
||||||
|
- name: kubeconfig
|
||||||
|
mountPath: /etc/kubernetes
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts/
|
||||||
|
readOnly: true
|
||||||
|
- name: k3s-agent
|
||||||
|
mountPath: /var/lib/rancher/k3s/agent/
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
volumes:
|
volumes:
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
hostPath:
|
emptyDir: {}
|
||||||
# since the above DaemonSets are dependant on the labels
|
- name: scripts
|
||||||
# and nkml would need a cni to start
|
configMap:
|
||||||
# it needs run on the hostnetwork and use the kubeconfig
|
name: kilo-scripts
|
||||||
# to label the nodes
|
- name: k3s-agent
|
||||||
path: /etc/rancher/k3s/k3s.yaml
|
hostPath:
|
||||||
|
path: /var/lib/rancher/k3s/agent
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
cni-conf.json: |
|
cni-conf.json: |
|
||||||
{
|
{
|
||||||
"cniVersion":"0.3.1",
|
"cniVersion":"0.4.0",
|
||||||
"name":"kilo",
|
"name":"kilo",
|
||||||
"plugins":[
|
"plugins":[
|
||||||
{
|
{
|
||||||
@@ -57,14 +57,13 @@ rules:
|
|||||||
- peers
|
- peers
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- update
|
|
||||||
- watch
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- apiextensions.k8s.io
|
- apiextensions.k8s.io
|
||||||
resources:
|
resources:
|
||||||
- customresourcedefinitions
|
- customresourcedefinitions
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- get
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
@@ -79,27 +78,60 @@ subjects:
|
|||||||
name: kilo
|
name: kilo
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
---
|
---
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: kilo-scripts
|
||||||
|
namespace: kube-system
|
||||||
|
data:
|
||||||
|
init.sh: |
|
||||||
|
#!/bin/sh
|
||||||
|
cat > /etc/kubernetes/kubeconfig <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Config
|
||||||
|
name: kilo
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
server: $(sed -n 's/.*server: \(.*\)/\1/p' /var/lib/rancher/k3s/agent/kubelet.kubeconfig)
|
||||||
|
certificate-authority: /var/lib/rancher/k3s/agent/server-ca.crt
|
||||||
|
users:
|
||||||
|
- name: kilo
|
||||||
|
user:
|
||||||
|
token: $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
||||||
|
contexts:
|
||||||
|
- name: kilo
|
||||||
|
context:
|
||||||
|
cluster: kilo
|
||||||
|
namespace: ${NAMESPACE}
|
||||||
|
user: kilo
|
||||||
|
current-context: kilo
|
||||||
|
EOF
|
||||||
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: DaemonSet
|
kind: DaemonSet
|
||||||
metadata:
|
metadata:
|
||||||
name: kilo
|
name: kilo
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo-userspace
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo-userspace
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo-userspace
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: kilo
|
serviceAccountName: kilo
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
containers:
|
containers:
|
||||||
- name: kilo
|
- name: kilo
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
args:
|
args:
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
@@ -110,6 +142,9 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
@@ -118,7 +153,7 @@ spec:
|
|||||||
- name: kilo-dir
|
- name: kilo-dir
|
||||||
mountPath: /var/lib/kilo
|
mountPath: /var/lib/kilo
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
mountPath: /etc/kubernetes/kubeconfig
|
mountPath: /etc/kubernetes
|
||||||
readOnly: true
|
readOnly: true
|
||||||
- name: lib-modules
|
- name: lib-modules
|
||||||
mountPath: /lib/modules
|
mountPath: /lib/modules
|
||||||
@@ -130,9 +165,9 @@ spec:
|
|||||||
mountPath: /var/run/wireguard
|
mountPath: /var/run/wireguard
|
||||||
readOnly: false
|
readOnly: false
|
||||||
- name: boringtun
|
- name: boringtun
|
||||||
image: leonnicolas/boringtun
|
image: leonnicolas/boringtun:cc19859
|
||||||
args:
|
args:
|
||||||
- --disable-drop-privileges=true
|
- --disable-drop-privileges
|
||||||
- --foreground
|
- --foreground
|
||||||
- kilo0
|
- kilo0
|
||||||
securityContext:
|
securityContext:
|
||||||
@@ -142,8 +177,29 @@ spec:
|
|||||||
mountPath: /var/run/wireguard
|
mountPath: /var/run/wireguard
|
||||||
readOnly: false
|
readOnly: false
|
||||||
initContainers:
|
initContainers:
|
||||||
|
- name: generate-kubeconfig
|
||||||
|
image: squat/kilo:0.5.0
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
args:
|
||||||
|
- /scripts/init.sh
|
||||||
|
imagePullPolicy: Always
|
||||||
|
volumeMounts:
|
||||||
|
- name: kubeconfig
|
||||||
|
mountPath: /etc/kubernetes
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts/
|
||||||
|
readOnly: true
|
||||||
|
- name: k3s-agent
|
||||||
|
mountPath: /var/lib/rancher/k3s/agent/
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
- name: install-cni
|
- name: install-cni
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
@@ -182,11 +238,13 @@ spec:
|
|||||||
hostPath:
|
hostPath:
|
||||||
path: /var/lib/kilo
|
path: /var/lib/kilo
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
hostPath:
|
emptyDir: {}
|
||||||
# Since kilo runs as a daemonset, it is recommended that you copy the
|
- name: scripts
|
||||||
# k3s.yaml kubeconfig file from the master node to all worker nodes
|
configMap:
|
||||||
# with the same path structure.
|
name: kilo-scripts
|
||||||
path: /etc/rancher/k3s/k3s.yaml
|
- name: k3s-agent
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/rancher/k3s/agent
|
||||||
- name: lib-modules
|
- name: lib-modules
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /lib/modules
|
path: /lib/modules
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
cni-conf.json: |
|
cni-conf.json: |
|
||||||
{
|
{
|
||||||
"cniVersion":"0.3.1",
|
"cniVersion":"0.4.0",
|
||||||
"name":"kilo",
|
"name":"kilo",
|
||||||
"plugins":[
|
"plugins":[
|
||||||
{
|
{
|
||||||
@@ -57,14 +57,13 @@ rules:
|
|||||||
- peers
|
- peers
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- update
|
|
||||||
- watch
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- apiextensions.k8s.io
|
- apiextensions.k8s.io
|
||||||
resources:
|
resources:
|
||||||
- customresourcedefinitions
|
- customresourcedefinitions
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- get
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
@@ -79,6 +78,35 @@ subjects:
|
|||||||
name: kilo
|
name: kilo
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
---
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: kilo-scripts
|
||||||
|
namespace: kube-system
|
||||||
|
data:
|
||||||
|
init.sh: |
|
||||||
|
#!/bin/sh
|
||||||
|
cat > /etc/kubernetes/kubeconfig <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Config
|
||||||
|
name: kilo
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
server: $(sed -n 's/.*server: \(.*\)/\1/p' /var/lib/rancher/k3s/agent/kubelet.kubeconfig)
|
||||||
|
certificate-authority: /var/lib/rancher/k3s/agent/server-ca.crt
|
||||||
|
users:
|
||||||
|
- name: kilo
|
||||||
|
user:
|
||||||
|
token: $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
||||||
|
contexts:
|
||||||
|
- name: kilo
|
||||||
|
context:
|
||||||
|
cluster: kilo
|
||||||
|
namespace: ${NAMESPACE}
|
||||||
|
user: kilo
|
||||||
|
current-context: kilo
|
||||||
|
EOF
|
||||||
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: DaemonSet
|
kind: DaemonSet
|
||||||
metadata:
|
metadata:
|
||||||
@@ -86,20 +114,23 @@ metadata:
|
|||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: kilo
|
serviceAccountName: kilo
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
containers:
|
containers:
|
||||||
- name: kilo
|
- name: kilo
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
args:
|
args:
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
@@ -108,6 +139,9 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
@@ -116,7 +150,7 @@ spec:
|
|||||||
- name: kilo-dir
|
- name: kilo-dir
|
||||||
mountPath: /var/lib/kilo
|
mountPath: /var/lib/kilo
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
mountPath: /etc/kubernetes/kubeconfig
|
mountPath: /etc/kubernetes
|
||||||
readOnly: true
|
readOnly: true
|
||||||
- name: lib-modules
|
- name: lib-modules
|
||||||
mountPath: /lib/modules
|
mountPath: /lib/modules
|
||||||
@@ -125,8 +159,29 @@ spec:
|
|||||||
mountPath: /run/xtables.lock
|
mountPath: /run/xtables.lock
|
||||||
readOnly: false
|
readOnly: false
|
||||||
initContainers:
|
initContainers:
|
||||||
|
- name: generate-kubeconfig
|
||||||
|
image: squat/kilo:0.5.0
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
args:
|
||||||
|
- /scripts/init.sh
|
||||||
|
imagePullPolicy: Always
|
||||||
|
volumeMounts:
|
||||||
|
- name: kubeconfig
|
||||||
|
mountPath: /etc/kubernetes
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts/
|
||||||
|
readOnly: true
|
||||||
|
- name: k3s-agent
|
||||||
|
mountPath: /var/lib/rancher/k3s/agent/
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
- name: install-cni
|
- name: install-cni
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
@@ -165,11 +220,13 @@ spec:
|
|||||||
hostPath:
|
hostPath:
|
||||||
path: /var/lib/kilo
|
path: /var/lib/kilo
|
||||||
- name: kubeconfig
|
- name: kubeconfig
|
||||||
hostPath:
|
emptyDir: {}
|
||||||
# Since kilo runs as a daemonset, it is recommended that you copy the
|
- name: scripts
|
||||||
# k3s.yaml kubeconfig file from the master node to all worker nodes
|
configMap:
|
||||||
# with the same path structure.
|
name: kilo-scripts
|
||||||
path: /etc/rancher/k3s/k3s.yaml
|
- name: k3s-agent
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/rancher/k3s/agent
|
||||||
- name: lib-modules
|
- name: lib-modules
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /lib/modules
|
path: /lib/modules
|
||||||
|
|||||||
142
manifests/kilo-kubeadm-cilium.yaml
Normal file
142
manifests/kilo-kubeadm-cilium.yaml
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- kilo.squat.ai
|
||||||
|
resources:
|
||||||
|
- peers
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- apiextensions.k8s.io
|
||||||
|
resources:
|
||||||
|
- customresourcedefinitions
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: kilo
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
spec:
|
||||||
|
serviceAccountName: kilo
|
||||||
|
hostNetwork: true
|
||||||
|
containers:
|
||||||
|
- name: kilo
|
||||||
|
image: squat/kilo:0.5.0
|
||||||
|
args:
|
||||||
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
|
- --hostname=$(NODE_NAME)
|
||||||
|
- --cni=false
|
||||||
|
- --compatibility=cilium
|
||||||
|
- --local=false
|
||||||
|
# additional and also optional flag
|
||||||
|
- --encapsulate=crosssubnet
|
||||||
|
- --clean-up-interface=true
|
||||||
|
- --subnet=172.31.254.0/24
|
||||||
|
- --log-level=all
|
||||||
|
env:
|
||||||
|
- name: NODE_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: kilo-dir
|
||||||
|
mountPath: /var/lib/kilo
|
||||||
|
|
||||||
|
# with kube-proxy configmap
|
||||||
|
# - name: kubeconfig
|
||||||
|
# mountPath: /etc/kubernetes
|
||||||
|
# readOnly: true
|
||||||
|
|
||||||
|
# without kube-proxy host kubeconfig binding
|
||||||
|
- name: kubeconfig
|
||||||
|
mount_path: /etc/kubernetes/kubeconfig
|
||||||
|
sub_path: admin.conf
|
||||||
|
read_only: true
|
||||||
|
|
||||||
|
- name: lib-modules
|
||||||
|
mountPath: /lib/modules
|
||||||
|
readOnly: true
|
||||||
|
- name: xtables-lock
|
||||||
|
mountPath: /run/xtables.lock
|
||||||
|
readOnly: false
|
||||||
|
tolerations:
|
||||||
|
- effect: NoSchedule
|
||||||
|
operator: Exists
|
||||||
|
- effect: NoExecute
|
||||||
|
operator: Exists
|
||||||
|
volumes:
|
||||||
|
- name: kilo-dir
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/kilo
|
||||||
|
|
||||||
|
# with kube-proxy configmap
|
||||||
|
# - name: kubeconfig
|
||||||
|
# configMap:
|
||||||
|
# name: kube-proxy
|
||||||
|
# items:
|
||||||
|
# - key: kubeconfig.conf
|
||||||
|
# path: kubeconfig
|
||||||
|
|
||||||
|
# without kube-proxy host kubeconfig binding
|
||||||
|
- name: kubeconfig
|
||||||
|
host_path:
|
||||||
|
path: /etc/kubernetes
|
||||||
|
|
||||||
|
- name: lib-modules
|
||||||
|
hostPath:
|
||||||
|
path: /lib/modules
|
||||||
|
- name: xtables-lock
|
||||||
|
hostPath:
|
||||||
|
path: /run/xtables.lock
|
||||||
|
type: FileOrCreate
|
||||||
142
manifests/kilo-kubeadm-flannel-userspace.yaml
Normal file
142
manifests/kilo-kubeadm-flannel-userspace.yaml
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- kilo.squat.ai
|
||||||
|
resources:
|
||||||
|
- peers
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- apiextensions.k8s.io
|
||||||
|
resources:
|
||||||
|
- customresourcedefinitions
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: kilo
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
spec:
|
||||||
|
serviceAccountName: kilo
|
||||||
|
hostNetwork: true
|
||||||
|
containers:
|
||||||
|
- name: boringtun
|
||||||
|
image: leonnicolas/boringtun:cc19859
|
||||||
|
args:
|
||||||
|
- --disable-drop-privileges=true
|
||||||
|
- --foreground
|
||||||
|
- kilo0
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: wireguard
|
||||||
|
mountPath: /var/run/wireguard
|
||||||
|
readOnly: false
|
||||||
|
- name: kilo
|
||||||
|
image: squat/kilo:0.5.0
|
||||||
|
args:
|
||||||
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
|
- --hostname=$(NODE_NAME)
|
||||||
|
- --create-interface=false
|
||||||
|
- --interface=kilo0
|
||||||
|
- --cni=false
|
||||||
|
- --compatibility=flannel
|
||||||
|
- --local=false
|
||||||
|
env:
|
||||||
|
- name: NODE_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: cni-conf-dir
|
||||||
|
mountPath: /etc/cni/net.d
|
||||||
|
- name: kilo-dir
|
||||||
|
mountPath: /var/lib/kilo
|
||||||
|
- name: lib-modules
|
||||||
|
mountPath: /lib/modules
|
||||||
|
readOnly: true
|
||||||
|
- name: xtables-lock
|
||||||
|
mountPath: /run/xtables.lock
|
||||||
|
readOnly: false
|
||||||
|
- name: wireguard
|
||||||
|
mountPath: /var/run/wireguard
|
||||||
|
readOnly: false
|
||||||
|
tolerations:
|
||||||
|
- operator: Exists
|
||||||
|
volumes:
|
||||||
|
- name: cni-bin-dir
|
||||||
|
hostPath:
|
||||||
|
path: /opt/cni/bin
|
||||||
|
- name: cni-conf-dir
|
||||||
|
hostPath:
|
||||||
|
path: /etc/cni/net.d
|
||||||
|
- name: kilo-dir
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/kilo
|
||||||
|
- name: kubeconfig
|
||||||
|
configMap:
|
||||||
|
name: kube-proxy
|
||||||
|
items:
|
||||||
|
- key: kubeconfig.conf
|
||||||
|
path: kubeconfig
|
||||||
|
- name: lib-modules
|
||||||
|
hostPath:
|
||||||
|
path: /lib/modules
|
||||||
|
- name: xtables-lock
|
||||||
|
hostPath:
|
||||||
|
path: /run/xtables.lock
|
||||||
|
type: FileOrCreate
|
||||||
|
- name: wireguard
|
||||||
|
hostPath:
|
||||||
|
path: /var/run/wireguard
|
||||||
@@ -23,14 +23,13 @@ rules:
|
|||||||
- peers
|
- peers
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- update
|
|
||||||
- watch
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- apiextensions.k8s.io
|
- apiextensions.k8s.io
|
||||||
resources:
|
resources:
|
||||||
- customresourcedefinitions
|
- customresourcedefinitions
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- get
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
@@ -52,20 +51,23 @@ metadata:
|
|||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: kilo
|
serviceAccountName: kilo
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
containers:
|
containers:
|
||||||
- name: kilo
|
- name: kilo
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
args:
|
args:
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
@@ -77,6 +79,9 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
|
|||||||
207
manifests/kilo-kubeadm-userspace.yaml
Normal file
207
manifests/kilo-kubeadm-userspace.yaml
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
data:
|
||||||
|
cni-conf.json: |
|
||||||
|
{
|
||||||
|
"cniVersion":"0.4.0",
|
||||||
|
"name":"kilo",
|
||||||
|
"plugins":[
|
||||||
|
{
|
||||||
|
"name":"kubernetes",
|
||||||
|
"type":"bridge",
|
||||||
|
"bridge":"kube-bridge",
|
||||||
|
"isDefaultGateway":true,
|
||||||
|
"forceAddress":true,
|
||||||
|
"mtu": 1420,
|
||||||
|
"ipam":{
|
||||||
|
"type":"host-local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type":"portmap",
|
||||||
|
"snat":true,
|
||||||
|
"capabilities":{
|
||||||
|
"portMappings":true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- kilo.squat.ai
|
||||||
|
resources:
|
||||||
|
- peers
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- apiextensions.k8s.io
|
||||||
|
resources:
|
||||||
|
- customresourcedefinitions
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: kilo
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
spec:
|
||||||
|
serviceAccountName: kilo
|
||||||
|
hostNetwork: true
|
||||||
|
containers:
|
||||||
|
- name: boringtun
|
||||||
|
image: leonnicolas/boringtun:cc19859
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
args:
|
||||||
|
- --disable-drop-privileges
|
||||||
|
- --foreground
|
||||||
|
- kilo0
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: wireguard
|
||||||
|
mountPath: /var/run/wireguard
|
||||||
|
readOnly: false
|
||||||
|
- name: kilo
|
||||||
|
image: squat/kilo:0.5.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
args:
|
||||||
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
|
- --hostname=$(NODE_NAME)
|
||||||
|
- --create-interface=false
|
||||||
|
- --interface=kilo0
|
||||||
|
env:
|
||||||
|
- name: NODE_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: cni-conf-dir
|
||||||
|
mountPath: /etc/cni/net.d
|
||||||
|
- name: kilo-dir
|
||||||
|
mountPath: /var/lib/kilo
|
||||||
|
- name: kubeconfig
|
||||||
|
mountPath: /etc/kubernetes
|
||||||
|
readOnly: true
|
||||||
|
- name: lib-modules
|
||||||
|
mountPath: /lib/modules
|
||||||
|
readOnly: true
|
||||||
|
- name: xtables-lock
|
||||||
|
mountPath: /run/xtables.lock
|
||||||
|
readOnly: false
|
||||||
|
- name: wireguard
|
||||||
|
mountPath: /var/run/wireguard
|
||||||
|
readOnly: false
|
||||||
|
initContainers:
|
||||||
|
- name: install-cni
|
||||||
|
image: squat/kilo:0.5.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- set -e -x;
|
||||||
|
cp /opt/cni/bin/* /host/opt/cni/bin/;
|
||||||
|
TMP_CONF="$CNI_CONF_NAME".tmp;
|
||||||
|
echo "$CNI_NETWORK_CONFIG" > $TMP_CONF;
|
||||||
|
rm -f /host/etc/cni/net.d/*;
|
||||||
|
mv $TMP_CONF /host/etc/cni/net.d/$CNI_CONF_NAME
|
||||||
|
env:
|
||||||
|
- name: CNI_CONF_NAME
|
||||||
|
value: 10-kilo.conflist
|
||||||
|
- name: CNI_NETWORK_CONFIG
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: kilo
|
||||||
|
key: cni-conf.json
|
||||||
|
volumeMounts:
|
||||||
|
- name: cni-bin-dir
|
||||||
|
mountPath: /host/opt/cni/bin
|
||||||
|
- name: cni-conf-dir
|
||||||
|
mountPath: /host/etc/cni/net.d
|
||||||
|
tolerations:
|
||||||
|
- effect: NoSchedule
|
||||||
|
operator: Exists
|
||||||
|
- effect: NoExecute
|
||||||
|
operator: Exists
|
||||||
|
volumes:
|
||||||
|
- name: cni-bin-dir
|
||||||
|
hostPath:
|
||||||
|
path: /opt/cni/bin
|
||||||
|
- name: cni-conf-dir
|
||||||
|
hostPath:
|
||||||
|
path: /etc/cni/net.d
|
||||||
|
- name: kilo-dir
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/kilo
|
||||||
|
- name: kubeconfig
|
||||||
|
configMap:
|
||||||
|
name: kube-proxy
|
||||||
|
items:
|
||||||
|
- key: kubeconfig.conf
|
||||||
|
path: kubeconfig
|
||||||
|
- name: lib-modules
|
||||||
|
hostPath:
|
||||||
|
path: /lib/modules
|
||||||
|
- name: xtables-lock
|
||||||
|
hostPath:
|
||||||
|
path: /run/xtables.lock
|
||||||
|
type: FileOrCreate
|
||||||
|
- name: wireguard
|
||||||
|
hostPath:
|
||||||
|
path: /var/run/wireguard
|
||||||
@@ -8,7 +8,7 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
cni-conf.json: |
|
cni-conf.json: |
|
||||||
{
|
{
|
||||||
"cniVersion":"0.3.1",
|
"cniVersion":"0.4.0",
|
||||||
"name":"kilo",
|
"name":"kilo",
|
||||||
"plugins":[
|
"plugins":[
|
||||||
{
|
{
|
||||||
@@ -57,14 +57,13 @@ rules:
|
|||||||
- peers
|
- peers
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- update
|
|
||||||
- watch
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- apiextensions.k8s.io
|
- apiextensions.k8s.io
|
||||||
resources:
|
resources:
|
||||||
- customresourcedefinitions
|
- customresourcedefinitions
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- get
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
@@ -86,20 +85,23 @@ metadata:
|
|||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: kilo
|
serviceAccountName: kilo
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
containers:
|
containers:
|
||||||
- name: kilo
|
- name: kilo
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
args:
|
args:
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
@@ -108,6 +110,9 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
@@ -126,7 +131,7 @@ spec:
|
|||||||
readOnly: false
|
readOnly: false
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: install-cni
|
- name: install-cni
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
|
|||||||
@@ -23,14 +23,13 @@ rules:
|
|||||||
- peers
|
- peers
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- update
|
|
||||||
- watch
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- apiextensions.k8s.io
|
- apiextensions.k8s.io
|
||||||
resources:
|
resources:
|
||||||
- customresourcedefinitions
|
- customresourcedefinitions
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- get
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
@@ -52,20 +51,23 @@ metadata:
|
|||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: kilo
|
serviceAccountName: kilo
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
containers:
|
containers:
|
||||||
- name: kilo
|
- name: kilo
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
args:
|
args:
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
@@ -77,6 +79,9 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
cni-conf.json: |
|
cni-conf.json: |
|
||||||
{
|
{
|
||||||
"cniVersion":"0.3.1",
|
"cniVersion":"0.4.0",
|
||||||
"name":"kilo",
|
"name":"kilo",
|
||||||
"plugins":[
|
"plugins":[
|
||||||
{
|
{
|
||||||
@@ -57,14 +57,13 @@ rules:
|
|||||||
- peers
|
- peers
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- update
|
|
||||||
- watch
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- apiextensions.k8s.io
|
- apiextensions.k8s.io
|
||||||
resources:
|
resources:
|
||||||
- customresourcedefinitions
|
- customresourcedefinitions
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- get
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
@@ -86,20 +85,23 @@ metadata:
|
|||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: kilo
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: kilo
|
serviceAccountName: kilo
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
containers:
|
containers:
|
||||||
- name: kilo
|
- name: kilo
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
args:
|
args:
|
||||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||||
- --hostname=$(NODE_NAME)
|
- --hostname=$(NODE_NAME)
|
||||||
@@ -108,6 +110,9 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
ports:
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
@@ -126,7 +131,7 @@ spec:
|
|||||||
readOnly: false
|
readOnly: false
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: install-cni
|
- name: install-cni
|
||||||
image: squat/kilo
|
image: squat/kilo:0.5.0
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
|
|||||||
173
manifests/peer-validation.yaml
Normal file
173
manifests/peer-validation.yaml
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: kilo
|
||||||
|
---
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1
|
||||||
|
kind: ValidatingWebhookConfiguration
|
||||||
|
metadata:
|
||||||
|
name: "peers.kilo.squat.ai"
|
||||||
|
webhooks:
|
||||||
|
- name: "peers.kilo.squat.ai"
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["kilo.squat.ai"]
|
||||||
|
apiVersions: ["v1alpha1"]
|
||||||
|
operations: ["CREATE","UPDATE"]
|
||||||
|
resources: ["peers"]
|
||||||
|
scope: "Cluster"
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
namespace: "kilo"
|
||||||
|
name: "peer-validation"
|
||||||
|
path: "/validate"
|
||||||
|
admissionReviewVersions: ["v1"]
|
||||||
|
sideEffects: None
|
||||||
|
timeoutSeconds: 5
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: peer-validation-server
|
||||||
|
namespace: kilo
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: peer-validation-server
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: peer-validation-server
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: peer-validation-server
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1000
|
||||||
|
containers:
|
||||||
|
- name: server
|
||||||
|
image: squat/kilo:0.5.0
|
||||||
|
args:
|
||||||
|
- webhook
|
||||||
|
- --cert-file=/run/secrets/tls/tls.crt
|
||||||
|
- --key-file=/run/secrets/tls/tls.key
|
||||||
|
- --listen-metrics=:1107
|
||||||
|
- --listen=:8443
|
||||||
|
ports:
|
||||||
|
- containerPort: 8443
|
||||||
|
name: webhook
|
||||||
|
- containerPort: 1107
|
||||||
|
name: metrics
|
||||||
|
volumeMounts:
|
||||||
|
- name: tls
|
||||||
|
mountPath: /run/secrets/tls
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: tls
|
||||||
|
secret:
|
||||||
|
secretName: peer-validation-webhook-tls
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: peer-validation
|
||||||
|
namespace: kilo
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: peer-validation-server
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
targetPort: webhook
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: kilo-peer-validation
|
||||||
|
namespace: kilo
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: kilo-peer-validation
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- admissionregistration.k8s.io
|
||||||
|
resources:
|
||||||
|
- validatingwebhookconfigurations
|
||||||
|
resourceNames:
|
||||||
|
- peers.kilo.squat.ai
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- update
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: kilo-peer-validation
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: kilo-peer-validation
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
namespace: kilo
|
||||||
|
name: kilo-peer-validation
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: kilo-peer-validation
|
||||||
|
namespace: kilo
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: kilo-peer-validation
|
||||||
|
namespace: kilo
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: kilo-peer-validation
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
namespace: kilo
|
||||||
|
name: kilo-peer-validation
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: cert-gen
|
||||||
|
namespace: kilo
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
serviceAccountName: kilo-peer-validation
|
||||||
|
initContainers:
|
||||||
|
- name: create
|
||||||
|
image: k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.0
|
||||||
|
args:
|
||||||
|
- create
|
||||||
|
- --namespace=kilo
|
||||||
|
- --secret-name=peer-validation-webhook-tls
|
||||||
|
- --host=peer-validation,peer-validation.kilo.svc
|
||||||
|
- --key-name=tls.key
|
||||||
|
- --cert-name=tls.crt
|
||||||
|
containers:
|
||||||
|
- name: patch
|
||||||
|
image: k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.0
|
||||||
|
args:
|
||||||
|
- patch
|
||||||
|
- --webhook-name=peers.kilo.squat.ai
|
||||||
|
- --secret-name=peer-validation-webhook-tls
|
||||||
|
- --namespace=kilo
|
||||||
|
- --patch-mutating=false
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
backoffLimit: 4
|
||||||
19
manifests/podmonitor.yaml
Normal file
19
manifests/podmonitor.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: PodMonitor
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: kilo
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
name: kilo
|
||||||
|
namespace: kilo
|
||||||
|
spec:
|
||||||
|
namespaceSelector:
|
||||||
|
matchNames:
|
||||||
|
- kube-system
|
||||||
|
podMetricsEndpoints:
|
||||||
|
- interval: 15s
|
||||||
|
port: metrics
|
||||||
|
path: /metrics
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
56
manifests/wg-exporter-role-kube-prometheus.yaml
Normal file
56
manifests/wg-exporter-role-kube-prometheus.yaml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: prometheus
|
||||||
|
app.kubernetes.io/name: prometheus
|
||||||
|
app.kubernetes.io/part-of: kube-prometheus
|
||||||
|
app.kubernetes.io/version: 2.26.0
|
||||||
|
name: prometheus-k8s
|
||||||
|
namespace: kilo
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
- endpoints
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- ingresses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- networking.k8s.io
|
||||||
|
resources:
|
||||||
|
- ingresses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: prometheus
|
||||||
|
app.kubernetes.io/name: prometheus
|
||||||
|
app.kubernetes.io/part-of: kube-prometheus
|
||||||
|
app.kubernetes.io/version: 2.26.0
|
||||||
|
name: prometheus-k8s
|
||||||
|
namespace: kilo
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: prometheus-k8s
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: prometheus-k8s
|
||||||
|
namespace: monitoring
|
||||||
67
manifests/wg-exporter.yaml
Normal file
67
manifests/wg-exporter.yaml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: PodMonitor
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: wg-exporter
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
name: wg-exporter
|
||||||
|
namespace: kilo
|
||||||
|
spec:
|
||||||
|
namespaceSelector:
|
||||||
|
matchNames:
|
||||||
|
- kilo
|
||||||
|
podMetricsEndpoints:
|
||||||
|
- interval: 15s
|
||||||
|
port: metrics
|
||||||
|
path: /metrics
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
app.kubernetes.io/name: wg-exporter
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: wg-exporter
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
name: wg-exporter
|
||||||
|
namespace: kilo
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: wg-exporter
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: wg-exporter
|
||||||
|
app.kubernetes.io/part-of: kilo
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- args:
|
||||||
|
- -a
|
||||||
|
- -i=kilo0
|
||||||
|
- -p=9586
|
||||||
|
image: mindflavor/prometheus-wireguard-exporter
|
||||||
|
name: wg-exporter
|
||||||
|
ports:
|
||||||
|
- containerPort: 9586
|
||||||
|
name: metrics
|
||||||
|
protocol: TCP
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
terminationMessagePath: /dev/termination-log
|
||||||
|
terminationMessagePolicy: File
|
||||||
|
volumeMounts:
|
||||||
|
- name: wireguard
|
||||||
|
mountPath: /var/run/wireguard
|
||||||
|
volumes:
|
||||||
|
- name: wireguard
|
||||||
|
hostPath:
|
||||||
|
path: /var/run/wireguard
|
||||||
|
tolerations:
|
||||||
|
- effect: NoSchedule
|
||||||
|
operator: Exists
|
||||||
|
- effect: NoExecute
|
||||||
|
operator: Exists
|
||||||
111
pkg/encapsulation/cilium.go
Normal file
111
pkg/encapsulation/cilium.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
// Copyright 2019 the Kilo authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package encapsulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
"github.com/squat/kilo/pkg/iptables"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ciliumDeviceName = "cilium_host"
|
||||||
|
|
||||||
|
type cilium struct {
|
||||||
|
iface int
|
||||||
|
strategy Strategy
|
||||||
|
ch chan netlink.LinkUpdate
|
||||||
|
done chan struct{}
|
||||||
|
// mu guards updates to the iface field.
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCilium returns an encapsulator that uses Cilium.
|
||||||
|
func NewCilium(strategy Strategy) Encapsulator {
|
||||||
|
return &cilium{
|
||||||
|
ch: make(chan netlink.LinkUpdate),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
strategy: strategy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp close done channel
|
||||||
|
func (f *cilium) CleanUp() error {
|
||||||
|
close(f.done)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gw returns the correct gateway IP associated with the given node.
|
||||||
|
func (f *cilium) Gw(_, _ net.IP, subnet *net.IPNet) net.IP {
|
||||||
|
return subnet.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index returns the index of the Cilium interface.
|
||||||
|
func (f *cilium) Index() int {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
return f.iface
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init finds the Cilium interface index.
|
||||||
|
func (f *cilium) Init(_ int) error {
|
||||||
|
if err := netlink.LinkSubscribe(f.ch, f.done); err != nil {
|
||||||
|
return fmt.Errorf("failed to subscribe to updates to %s: %v", ciliumDeviceName, err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
var lu netlink.LinkUpdate
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case lu = <-f.ch:
|
||||||
|
if lu.Attrs().Name == ciliumDeviceName {
|
||||||
|
f.mu.Lock()
|
||||||
|
f.iface = lu.Attrs().Index
|
||||||
|
f.mu.Unlock()
|
||||||
|
}
|
||||||
|
case <-f.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
i, err := netlink.LinkByName(ciliumDeviceName)
|
||||||
|
if _, ok := err.(netlink.LinkNotFoundError); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to query for Cilium interface: %v", err)
|
||||||
|
}
|
||||||
|
f.mu.Lock()
|
||||||
|
f.iface = i.Attrs().Index
|
||||||
|
f.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rules is a no-op.
|
||||||
|
func (f *cilium) Rules(_ []*net.IPNet) []iptables.Rule {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is a no-op.
|
||||||
|
func (f *cilium) Set(_ *net.IPNet) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy returns the configured strategy for encapsulation.
|
||||||
|
func (f *cilium) Strategy() Strategy {
|
||||||
|
return f.strategy
|
||||||
|
}
|
||||||
@@ -56,6 +56,8 @@ func (f *flannel) Gw(_, _ net.IP, subnet *net.IPNet) net.IP {
|
|||||||
|
|
||||||
// Index returns the index of the Flannel interface.
|
// Index returns the index of the Flannel interface.
|
||||||
func (f *flannel) Index() int {
|
func (f *flannel) Index() int {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
return f.iface
|
return f.iface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func (i *ipip) Rules(nodes []*net.IPNet) []iptables.Rule {
|
|||||||
rules = append(rules, iptables.NewIPv6Rule("filter", "INPUT", "-p", proto, "-m", "comment", "--comment", "Kilo: jump to IPIP chain", "-j", "KILO-IPIP"))
|
rules = append(rules, iptables.NewIPv6Rule("filter", "INPUT", "-p", proto, "-m", "comment", "--comment", "Kilo: jump to IPIP chain", "-j", "KILO-IPIP"))
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
// Accept encapsulated traffic from peers.
|
// Accept encapsulated traffic from peers.
|
||||||
rules = append(rules, iptables.NewRule(iptables.GetProtocol(len(n.IP)), "filter", "KILO-IPIP", "-s", n.String(), "-m", "comment", "--comment", "Kilo: allow IPIP traffic", "-j", "ACCEPT"))
|
rules = append(rules, iptables.NewRule(iptables.GetProtocol(n.IP), "filter", "KILO-IPIP", "-s", n.String(), "-m", "comment", "--comment", "Kilo: allow IPIP traffic", "-j", "ACCEPT"))
|
||||||
}
|
}
|
||||||
// Drop all other IPIP traffic.
|
// Drop all other IPIP traffic.
|
||||||
rules = append(rules, iptables.NewIPv4Rule("filter", "INPUT", "-p", proto, "-m", "comment", "--comment", "Kilo: reject other IPIP traffic", "-j", "DROP"))
|
rules = append(rules, iptables.NewIPv4Rule("filter", "INPUT", "-p", proto, "-m", "comment", "--comment", "Kilo: reject other IPIP traffic", "-j", "DROP"))
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build cgo
|
||||||
// +build cgo
|
// +build cgo
|
||||||
|
|
||||||
package encapsulation
|
package encapsulation
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !cgo
|
||||||
// +build !cgo
|
// +build !cgo
|
||||||
|
|
||||||
package encapsulation
|
package encapsulation
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ package iptables
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -25,6 +27,21 @@ import (
|
|||||||
"github.com/go-kit/kit/log/level"
|
"github.com/go-kit/kit/log/level"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const ipv6ModuleDisabledPath = "/sys/module/ipv6/parameters/disable"
|
||||||
|
|
||||||
|
func ipv6Disabled() (bool, error) {
|
||||||
|
f, err := os.Open(ipv6ModuleDisabledPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
disabled := make([]byte, 1)
|
||||||
|
if _, err = io.ReadFull(f, disabled); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return disabled[0] == '1', nil
|
||||||
|
}
|
||||||
|
|
||||||
// Protocol represents an IP protocol.
|
// Protocol represents an IP protocol.
|
||||||
type Protocol byte
|
type Protocol byte
|
||||||
|
|
||||||
@@ -36,11 +53,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// GetProtocol will return a protocol from the length of an IP address.
|
// GetProtocol will return a protocol from the length of an IP address.
|
||||||
func GetProtocol(length int) Protocol {
|
func GetProtocol(ip net.IP) Protocol {
|
||||||
if length == net.IPv6len {
|
if len(ip) == net.IPv4len || ip.To4() != nil {
|
||||||
return ProtocolIPv6
|
return ProtocolIPv4
|
||||||
}
|
}
|
||||||
return ProtocolIPv4
|
return ProtocolIPv6
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client represents any type that can administer iptables rules.
|
// Client represents any type that can administer iptables rules.
|
||||||
@@ -253,11 +270,20 @@ func New(opts ...ControllerOption) (*Controller, error) {
|
|||||||
c.v4 = v4
|
c.v4 = v4
|
||||||
}
|
}
|
||||||
if c.v6 == nil {
|
if c.v6 == nil {
|
||||||
v6, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
disabled, err := ipv6Disabled()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create iptables IPv6 client: %v", err)
|
return nil, fmt.Errorf("failed to check IPv6 status: %v", err)
|
||||||
|
}
|
||||||
|
if disabled {
|
||||||
|
level.Info(c.logger).Log("msg", "IPv6 is disabled in the kernel; disabling the IPv6 iptables controller")
|
||||||
|
c.v6 = &fakeClient{}
|
||||||
|
} else {
|
||||||
|
v6, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create iptables IPv6 client: %v", err)
|
||||||
|
}
|
||||||
|
c.v6 = v6
|
||||||
}
|
}
|
||||||
c.v6 = v6
|
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,5 +16,5 @@ package kilo
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// GroupName contains the API group name for Kilo API group.
|
// GroupName contains the API group name for Kilo API group.
|
||||||
GroupName = "kilo"
|
GroupName = "kilo.squat.ai"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,5 +15,5 @@
|
|||||||
// +k8s:deepcopy-gen=package,register
|
// +k8s:deepcopy-gen=package,register
|
||||||
|
|
||||||
// Package v1alpha1 is the v1alpha1 version of the API.
|
// Package v1alpha1 is the v1alpha1 version of the API.
|
||||||
// +groupName=kilo
|
// +groupName=kilo.squat.ai
|
||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -47,6 +48,7 @@ var PeerShortNames = []string{"peer"}
|
|||||||
// +genclient:nonNamespaced
|
// +genclient:nonNamespaced
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
|
// +kubebuilder:resource:scope=Cluster
|
||||||
|
|
||||||
// Peer is a WireGuard peer that should have access to the VPN.
|
// Peer is a WireGuard peer that should have access to the VPN.
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
@@ -76,20 +78,21 @@ type PeerSpec struct {
|
|||||||
PersistentKeepalive int `json:"persistentKeepalive,omitempty"`
|
PersistentKeepalive int `json:"persistentKeepalive,omitempty"`
|
||||||
// PresharedKey is the optional symmetric encryption key for the peer.
|
// PresharedKey is the optional symmetric encryption key for the peer.
|
||||||
// +optional
|
// +optional
|
||||||
PresharedKey string `json:"presharedKey"`
|
PresharedKey string `json:"presharedKey,omitempty"`
|
||||||
// PublicKey is the WireGuard public key for the peer.
|
// PublicKey is the WireGuard public key for the peer.
|
||||||
PublicKey string `json:"publicKey"`
|
PublicKey string `json:"publicKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerEndpoint represents a WireGuard enpoint, which is a ip:port tuple.
|
// PeerEndpoint represents a WireGuard endpoint, which is an IP:port tuple.
|
||||||
type PeerEndpoint struct {
|
type PeerEndpoint struct {
|
||||||
DNSOrIP
|
// DNSOrIP is a DNS name or an IP address.
|
||||||
|
DNSOrIP `json:"dnsOrIP"`
|
||||||
// Port must be a valid port number.
|
// Port must be a valid port number.
|
||||||
Port uint32 `json:"port"`
|
Port uint32 `json:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNSOrIP represents either a DNS name or an IP address.
|
// DNSOrIP represents either a DNS name or an IP address.
|
||||||
// IPs, as they are more specific, are preferred.
|
// When both are given, the IP address, as it is more specific, override the DNS name.
|
||||||
type DNSOrIP struct {
|
type DNSOrIP struct {
|
||||||
// DNS must be a valid RFC 1123 subdomain.
|
// DNS must be a valid RFC 1123 subdomain.
|
||||||
// +optional
|
// +optional
|
||||||
@@ -133,7 +136,7 @@ func (p *Peer) Copy() *Peer {
|
|||||||
func (p *Peer) Validate() error {
|
func (p *Peer) Validate() error {
|
||||||
for _, ip := range p.Spec.AllowedIPs {
|
for _, ip := range p.Spec.AllowedIPs {
|
||||||
if _, n, err := net.ParseCIDR(ip); err != nil {
|
if _, n, err := net.ParseCIDR(ip); err != nil {
|
||||||
return fmt.Errorf("failed to parse %q as a valid IP address: %v", ip, err)
|
return fmt.Errorf("failed to parse %q as a valid IP address: %w", ip, err)
|
||||||
} else if n == nil {
|
} else if n == nil {
|
||||||
return fmt.Errorf("got invalid IP address for %q", ip)
|
return fmt.Errorf("got invalid IP address for %q", ip)
|
||||||
}
|
}
|
||||||
@@ -157,8 +160,11 @@ func (p *Peer) Validate() error {
|
|||||||
if p.Spec.PersistentKeepalive < 0 {
|
if p.Spec.PersistentKeepalive < 0 {
|
||||||
return fmt.Errorf("persistent keepalive must be greater than or equal to zero; got %q", p.Spec.PersistentKeepalive)
|
return fmt.Errorf("persistent keepalive must be greater than or equal to zero; got %q", p.Spec.PersistentKeepalive)
|
||||||
}
|
}
|
||||||
if len(p.Spec.PublicKey) == 0 {
|
if b, err := base64.StdEncoding.DecodeString(p.Spec.PublicKey); err != nil {
|
||||||
return errors.New("public keys cannot be empty")
|
return fmt.Errorf("WireGuard public key is not base64 encoded: %w", err)
|
||||||
|
// Since WireGuard is using Curve25519 for the key exchange, the key length of 256 bits should not change in the near future.
|
||||||
|
} else if len(b) != 32 {
|
||||||
|
return errors.New("WireGuard public key has invalid length")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -168,6 +174,9 @@ func (p *Peer) Validate() error {
|
|||||||
// PeerList is a list of peers.
|
// PeerList is a list of peers.
|
||||||
type PeerList struct {
|
type PeerList struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
// Standard list metadata.
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||||
|
// +optional
|
||||||
metav1.ListMeta `json:"metadata,omitempty"`
|
metav1.ListMeta `json:"metadata,omitempty"`
|
||||||
// List of peers.
|
// List of peers.
|
||||||
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md
|
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -86,7 +87,7 @@ func (in *PeerEndpoint) DeepCopy() *PeerEndpoint {
|
|||||||
func (in *PeerList) DeepCopyInto(out *PeerList) {
|
func (in *PeerList) DeepCopyInto(out *PeerList) {
|
||||||
*out = *in
|
*out = *in
|
||||||
out.TypeMeta = in.TypeMeta
|
out.TypeMeta = in.TypeMeta
|
||||||
out.ListMeta = in.ListMeta
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
if in.Items != nil {
|
if in.Items != nil {
|
||||||
in, out := &in.Items, &out.Items
|
in, out := &in.Items, &out.Items
|
||||||
*out = make([]Peer, len(*in))
|
*out = make([]Peer, len(*in))
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package k8s
|
package k8s
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -24,15 +25,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
crdutils "github.com/ant31/crd-validation/pkg"
|
"github.com/go-kit/kit/log"
|
||||||
|
"github.com/go-kit/kit/log/level"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
|
||||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
|
||||||
v1informers "k8s.io/client-go/informers/core/v1"
|
v1informers "k8s.io/client-go/informers/core/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
v1listers "k8s.io/client-go/listers/core/v1"
|
v1listers "k8s.io/client-go/listers/core/v1"
|
||||||
@@ -59,12 +60,17 @@ const (
|
|||||||
locationAnnotationKey = "kilo.squat.ai/location"
|
locationAnnotationKey = "kilo.squat.ai/location"
|
||||||
persistentKeepaliveKey = "kilo.squat.ai/persistent-keepalive"
|
persistentKeepaliveKey = "kilo.squat.ai/persistent-keepalive"
|
||||||
wireGuardIPAnnotationKey = "kilo.squat.ai/wireguard-ip"
|
wireGuardIPAnnotationKey = "kilo.squat.ai/wireguard-ip"
|
||||||
|
discoveredEndpointsKey = "kilo.squat.ai/discovered-endpoints"
|
||||||
|
allowedLocationIPsKey = "kilo.squat.ai/allowed-location-ips"
|
||||||
|
granularityKey = "kilo.squat.ai/granularity"
|
||||||
// RegionLabelKey is the key for the well-known Kubernetes topology region label.
|
// RegionLabelKey is the key for the well-known Kubernetes topology region label.
|
||||||
RegionLabelKey = "topology.kubernetes.io/region"
|
RegionLabelKey = "topology.kubernetes.io/region"
|
||||||
jsonPatchSlash = "~1"
|
jsonPatchSlash = "~1"
|
||||||
jsonRemovePatch = `{"op": "remove", "path": "%s"}`
|
jsonRemovePatch = `{"op": "remove", "path": "%s"}`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var logger = log.NewNopLogger()
|
||||||
|
|
||||||
type backend struct {
|
type backend struct {
|
||||||
nodes *nodeBackend
|
nodes *nodeBackend
|
||||||
peers *peerBackend
|
peers *peerBackend
|
||||||
@@ -97,10 +103,12 @@ type peerBackend struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new instance of a mesh.Backend.
|
// New creates a new instance of a mesh.Backend.
|
||||||
func New(c kubernetes.Interface, kc kiloclient.Interface, ec apiextensions.Interface, topologyLabel string) mesh.Backend {
|
func New(c kubernetes.Interface, kc kiloclient.Interface, ec apiextensions.Interface, topologyLabel string, l log.Logger) mesh.Backend {
|
||||||
ni := v1informers.NewNodeInformer(c, 5*time.Minute, nil)
|
ni := v1informers.NewNodeInformer(c, 5*time.Minute, nil)
|
||||||
pi := v1alpha1informers.NewPeerInformer(kc, 5*time.Minute, nil)
|
pi := v1alpha1informers.NewPeerInformer(kc, 5*time.Minute, nil)
|
||||||
|
|
||||||
|
logger = l
|
||||||
|
|
||||||
return &backend{
|
return &backend{
|
||||||
&nodeBackend{
|
&nodeBackend{
|
||||||
client: c,
|
client: c,
|
||||||
@@ -120,15 +128,17 @@ func New(c kubernetes.Interface, kc kiloclient.Interface, ec apiextensions.Inter
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp removes configuration applied to the backend.
|
// CleanUp removes configuration applied to the backend.
|
||||||
func (nb *nodeBackend) CleanUp(name string) error {
|
func (nb *nodeBackend) CleanUp(ctx context.Context, name string) error {
|
||||||
patch := []byte("[" + strings.Join([]string{
|
patch := []byte("[" + strings.Join([]string{
|
||||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(endpointAnnotationKey, "/", jsonPatchSlash, 1))),
|
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(endpointAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(internalIPAnnotationKey, "/", jsonPatchSlash, 1))),
|
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(internalIPAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(keyAnnotationKey, "/", jsonPatchSlash, 1))),
|
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(keyAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(lastSeenAnnotationKey, "/", jsonPatchSlash, 1))),
|
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(lastSeenAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(wireGuardIPAnnotationKey, "/", jsonPatchSlash, 1))),
|
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(wireGuardIPAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||||
|
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(discoveredEndpointsKey, "/", jsonPatchSlash, 1))),
|
||||||
|
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(granularityKey, "/", jsonPatchSlash, 1))),
|
||||||
}, ",") + "]")
|
}, ",") + "]")
|
||||||
if _, err := nb.client.CoreV1().Nodes().Patch(name, types.JSONPatchType, patch); err != nil {
|
if _, err := nb.client.CoreV1().Nodes().Patch(ctx, name, types.JSONPatchType, patch, metav1.PatchOptions{}); err != nil {
|
||||||
return fmt.Errorf("failed to patch node: %v", err)
|
return fmt.Errorf("failed to patch node: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -145,9 +155,9 @@ func (nb *nodeBackend) Get(name string) (*mesh.Node, error) {
|
|||||||
|
|
||||||
// Init initializes the backend; for this backend that means
|
// Init initializes the backend; for this backend that means
|
||||||
// syncing the informer cache.
|
// syncing the informer cache.
|
||||||
func (nb *nodeBackend) Init(stop <-chan struct{}) error {
|
func (nb *nodeBackend) Init(ctx context.Context) error {
|
||||||
go nb.informer.Run(stop)
|
go nb.informer.Run(ctx.Done())
|
||||||
if ok := cache.WaitForCacheSync(stop, func() bool {
|
if ok := cache.WaitForCacheSync(ctx.Done(), func() bool {
|
||||||
return nb.informer.HasSynced()
|
return nb.informer.HasSynced()
|
||||||
}); !ok {
|
}); !ok {
|
||||||
return errors.New("failed to sync node cache")
|
return errors.New("failed to sync node cache")
|
||||||
@@ -202,7 +212,7 @@ func (nb *nodeBackend) List() ([]*mesh.Node, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set sets the fields of a node.
|
// Set sets the fields of a node.
|
||||||
func (nb *nodeBackend) Set(name string, node *mesh.Node) error {
|
func (nb *nodeBackend) Set(ctx context.Context, name string, node *mesh.Node) error {
|
||||||
old, err := nb.lister.Get(name)
|
old, err := nb.lister.Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to find node: %v", err)
|
return fmt.Errorf("failed to find node: %v", err)
|
||||||
@@ -214,13 +224,23 @@ func (nb *nodeBackend) Set(name string, node *mesh.Node) error {
|
|||||||
} else {
|
} else {
|
||||||
n.ObjectMeta.Annotations[internalIPAnnotationKey] = node.InternalIP.String()
|
n.ObjectMeta.Annotations[internalIPAnnotationKey] = node.InternalIP.String()
|
||||||
}
|
}
|
||||||
n.ObjectMeta.Annotations[keyAnnotationKey] = string(node.Key)
|
n.ObjectMeta.Annotations[keyAnnotationKey] = node.Key.String()
|
||||||
n.ObjectMeta.Annotations[lastSeenAnnotationKey] = strconv.FormatInt(node.LastSeen, 10)
|
n.ObjectMeta.Annotations[lastSeenAnnotationKey] = strconv.FormatInt(node.LastSeen, 10)
|
||||||
if node.WireGuardIP == nil {
|
if node.WireGuardIP == nil {
|
||||||
n.ObjectMeta.Annotations[wireGuardIPAnnotationKey] = ""
|
n.ObjectMeta.Annotations[wireGuardIPAnnotationKey] = ""
|
||||||
} else {
|
} else {
|
||||||
n.ObjectMeta.Annotations[wireGuardIPAnnotationKey] = node.WireGuardIP.String()
|
n.ObjectMeta.Annotations[wireGuardIPAnnotationKey] = node.WireGuardIP.String()
|
||||||
}
|
}
|
||||||
|
if node.DiscoveredEndpoints == nil {
|
||||||
|
n.ObjectMeta.Annotations[discoveredEndpointsKey] = ""
|
||||||
|
} else {
|
||||||
|
discoveredEndpoints, err := json.Marshal(node.DiscoveredEndpoints)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.ObjectMeta.Annotations[discoveredEndpointsKey] = string(discoveredEndpoints)
|
||||||
|
}
|
||||||
|
n.ObjectMeta.Annotations[granularityKey] = string(node.Granularity)
|
||||||
oldData, err := json.Marshal(old)
|
oldData, err := json.Marshal(old)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -233,7 +253,7 @@ func (nb *nodeBackend) Set(name string, node *mesh.Node) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create patch for node %q: %v", n.Name, err)
|
return fmt.Errorf("failed to create patch for node %q: %v", n.Name, err)
|
||||||
}
|
}
|
||||||
if _, err = nb.client.CoreV1().Nodes().Patch(name, types.StrategicMergePatchType, patch); err != nil {
|
if _, err = nb.client.CoreV1().Nodes().Patch(ctx, name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}); err != nil {
|
||||||
return fmt.Errorf("failed to patch node: %v", err)
|
return fmt.Errorf("failed to patch node: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -262,9 +282,9 @@ func translateNode(node *v1.Node, topologyLabel string) *mesh.Node {
|
|||||||
location = node.ObjectMeta.Labels[topologyLabel]
|
location = node.ObjectMeta.Labels[topologyLabel]
|
||||||
}
|
}
|
||||||
// Allow the endpoint to be overridden.
|
// Allow the endpoint to be overridden.
|
||||||
endpoint := parseEndpoint(node.ObjectMeta.Annotations[forceEndpointAnnotationKey])
|
endpoint := wireguard.ParseEndpoint(node.ObjectMeta.Annotations[forceEndpointAnnotationKey])
|
||||||
if endpoint == nil {
|
if endpoint == nil {
|
||||||
endpoint = parseEndpoint(node.ObjectMeta.Annotations[endpointAnnotationKey])
|
endpoint = wireguard.ParseEndpoint(node.ObjectMeta.Annotations[endpointAnnotationKey])
|
||||||
}
|
}
|
||||||
// Allow the internal IP to be overridden.
|
// Allow the internal IP to be overridden.
|
||||||
internalIP := normalizeIP(node.ObjectMeta.Annotations[forceInternalIPAnnotationKey])
|
internalIP := normalizeIP(node.ObjectMeta.Annotations[forceInternalIPAnnotationKey])
|
||||||
@@ -278,13 +298,11 @@ func translateNode(node *v1.Node, topologyLabel string) *mesh.Node {
|
|||||||
internalIP = nil
|
internalIP = nil
|
||||||
}
|
}
|
||||||
// Set Wireguard PersistentKeepalive setting for the node.
|
// Set Wireguard PersistentKeepalive setting for the node.
|
||||||
var persistentKeepalive int64
|
var persistentKeepalive time.Duration
|
||||||
if keepAlive, ok := node.ObjectMeta.Annotations[persistentKeepaliveKey]; !ok {
|
if keepAlive, ok := node.ObjectMeta.Annotations[persistentKeepaliveKey]; ok {
|
||||||
persistentKeepalive = 0
|
// We can ignore the error, because p will be set to 0 if an error occures.
|
||||||
} else {
|
p, _ := strconv.ParseInt(keepAlive, 10, 64)
|
||||||
if persistentKeepalive, err = strconv.ParseInt(keepAlive, 10, 64); err != nil {
|
persistentKeepalive = time.Duration(p) * time.Second
|
||||||
persistentKeepalive = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var lastSeen int64
|
var lastSeen int64
|
||||||
if ls, ok := node.ObjectMeta.Annotations[lastSeenAnnotationKey]; !ok {
|
if ls, ok := node.ObjectMeta.Annotations[lastSeenAnnotationKey]; !ok {
|
||||||
@@ -294,6 +312,36 @@ func translateNode(node *v1.Node, topologyLabel string) *mesh.Node {
|
|||||||
lastSeen = 0
|
lastSeen = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var discoveredEndpoints map[string]*net.UDPAddr
|
||||||
|
if de, ok := node.ObjectMeta.Annotations[discoveredEndpointsKey]; ok {
|
||||||
|
err := json.Unmarshal([]byte(de), &discoveredEndpoints)
|
||||||
|
if err != nil {
|
||||||
|
discoveredEndpoints = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set allowed IPs for a location.
|
||||||
|
var allowedLocationIPs []net.IPNet
|
||||||
|
if str, ok := node.ObjectMeta.Annotations[allowedLocationIPsKey]; ok {
|
||||||
|
for _, ip := range strings.Split(str, ",") {
|
||||||
|
if ipnet := normalizeIP(ip); ipnet != nil {
|
||||||
|
allowedLocationIPs = append(allowedLocationIPs, *ipnet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var meshGranularity mesh.Granularity
|
||||||
|
if gr, ok := node.ObjectMeta.Annotations[granularityKey]; ok {
|
||||||
|
meshGranularity = mesh.Granularity(gr)
|
||||||
|
switch meshGranularity {
|
||||||
|
case mesh.LogicalGranularity:
|
||||||
|
case mesh.FullGranularity:
|
||||||
|
default:
|
||||||
|
meshGranularity = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO log some error or warning.
|
||||||
|
key, _ := wgtypes.ParseKey(node.ObjectMeta.Annotations[keyAnnotationKey])
|
||||||
|
|
||||||
return &mesh.Node{
|
return &mesh.Node{
|
||||||
// Endpoint and InternalIP should only ever fail to parse if the
|
// Endpoint and InternalIP should only ever fail to parse if the
|
||||||
// remote node's agent has not yet set its IP address;
|
// remote node's agent has not yet set its IP address;
|
||||||
@@ -304,17 +352,20 @@ func translateNode(node *v1.Node, topologyLabel string) *mesh.Node {
|
|||||||
Endpoint: endpoint,
|
Endpoint: endpoint,
|
||||||
NoInternalIP: noInternalIP,
|
NoInternalIP: noInternalIP,
|
||||||
InternalIP: internalIP,
|
InternalIP: internalIP,
|
||||||
Key: []byte(node.ObjectMeta.Annotations[keyAnnotationKey]),
|
Key: key,
|
||||||
LastSeen: lastSeen,
|
LastSeen: lastSeen,
|
||||||
Leader: leader,
|
Leader: leader,
|
||||||
Location: location,
|
Location: location,
|
||||||
Name: node.Name,
|
Name: node.Name,
|
||||||
PersistentKeepalive: int(persistentKeepalive),
|
PersistentKeepalive: persistentKeepalive,
|
||||||
Subnet: subnet,
|
Subnet: subnet,
|
||||||
// WireGuardIP can fail to parse if the node is not a leader or if
|
// WireGuardIP can fail to parse if the node is not a leader or if
|
||||||
// the node's agent has not yet reconciled. In either case, the IP
|
// the node's agent has not yet reconciled. In either case, the IP
|
||||||
// will parse as nil.
|
// will parse as nil.
|
||||||
WireGuardIP: normalizeIP(node.ObjectMeta.Annotations[wireGuardIPAnnotationKey]),
|
WireGuardIP: normalizeIP(node.ObjectMeta.Annotations[wireGuardIPAnnotationKey]),
|
||||||
|
DiscoveredEndpoints: discoveredEndpoints,
|
||||||
|
AllowedLocationIPs: allowedLocationIPs,
|
||||||
|
Granularity: meshGranularity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,14 +374,14 @@ func translatePeer(peer *v1alpha1.Peer) *mesh.Peer {
|
|||||||
if peer == nil {
|
if peer == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var aips []*net.IPNet
|
var aips []net.IPNet
|
||||||
for _, aip := range peer.Spec.AllowedIPs {
|
for _, aip := range peer.Spec.AllowedIPs {
|
||||||
aip := normalizeIP(aip)
|
aip := normalizeIP(aip)
|
||||||
// Skip any invalid IPs.
|
// Skip any invalid IPs.
|
||||||
if aip == nil {
|
if aip == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
aips = append(aips, aip)
|
aips = append(aips, *aip)
|
||||||
}
|
}
|
||||||
var endpoint *wireguard.Endpoint
|
var endpoint *wireguard.Endpoint
|
||||||
if peer.Spec.Endpoint != nil {
|
if peer.Spec.Endpoint != nil {
|
||||||
@@ -340,42 +391,47 @@ func translatePeer(peer *v1alpha1.Peer) *mesh.Peer {
|
|||||||
} else {
|
} else {
|
||||||
ip = ip.To16()
|
ip = ip.To16()
|
||||||
}
|
}
|
||||||
if peer.Spec.Endpoint.Port > 0 && (ip != nil || peer.Spec.Endpoint.DNS != "") {
|
if peer.Spec.Endpoint.Port > 0 {
|
||||||
endpoint = &wireguard.Endpoint{
|
if ip != nil {
|
||||||
DNSOrIP: wireguard.DNSOrIP{
|
endpoint = wireguard.NewEndpoint(ip, int(peer.Spec.Endpoint.Port))
|
||||||
DNS: peer.Spec.Endpoint.DNS,
|
}
|
||||||
IP: ip,
|
if peer.Spec.Endpoint.DNS != "" {
|
||||||
},
|
endpoint = wireguard.ParseEndpoint(fmt.Sprintf("%s:%d", peer.Spec.Endpoint.DNS, peer.Spec.Endpoint.Port))
|
||||||
Port: peer.Spec.Endpoint.Port,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var key []byte
|
|
||||||
if len(peer.Spec.PublicKey) > 0 {
|
key, err := wgtypes.ParseKey(peer.Spec.PublicKey)
|
||||||
key = []byte(peer.Spec.PublicKey)
|
if err != nil {
|
||||||
|
level.Error(logger).Log("msg", "failed to parse public key", "peer", peer.Name, "err", err.Error())
|
||||||
}
|
}
|
||||||
var psk []byte
|
var psk *wgtypes.Key
|
||||||
if len(peer.Spec.PresharedKey) > 0 {
|
if k, err := wgtypes.ParseKey(peer.Spec.PresharedKey); err != nil {
|
||||||
psk = []byte(peer.Spec.PresharedKey)
|
// Set key to nil to avoid setting a key to the zero value wgtypes.Key{}
|
||||||
|
psk = nil
|
||||||
|
} else {
|
||||||
|
psk = &k
|
||||||
}
|
}
|
||||||
var pka int
|
var pka time.Duration
|
||||||
if peer.Spec.PersistentKeepalive > 0 {
|
if peer.Spec.PersistentKeepalive > 0 {
|
||||||
pka = peer.Spec.PersistentKeepalive
|
pka = time.Duration(peer.Spec.PersistentKeepalive) * time.Second
|
||||||
}
|
}
|
||||||
return &mesh.Peer{
|
return &mesh.Peer{
|
||||||
Name: peer.Name,
|
Name: peer.Name,
|
||||||
Peer: wireguard.Peer{
|
Peer: wireguard.Peer{
|
||||||
AllowedIPs: aips,
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
Endpoint: endpoint,
|
AllowedIPs: aips,
|
||||||
PersistentKeepalive: pka,
|
PersistentKeepaliveInterval: &pka,
|
||||||
PresharedKey: psk,
|
PresharedKey: psk,
|
||||||
PublicKey: key,
|
PublicKey: key,
|
||||||
|
},
|
||||||
|
Endpoint: endpoint,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp removes configuration applied to the backend.
|
// CleanUp removes configuration applied to the backend.
|
||||||
func (pb *peerBackend) CleanUp(name string) error {
|
func (pb *peerBackend) CleanUp(_ context.Context, _ string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,29 +446,14 @@ func (pb *peerBackend) Get(name string) (*mesh.Peer, error) {
|
|||||||
|
|
||||||
// Init initializes the backend; for this backend that means
|
// Init initializes the backend; for this backend that means
|
||||||
// syncing the informer cache.
|
// syncing the informer cache.
|
||||||
func (pb *peerBackend) Init(stop <-chan struct{}) error {
|
func (pb *peerBackend) Init(ctx context.Context) error {
|
||||||
// Register CRD.
|
// Check the presents of the CRD peers.kilo.squat.ai.
|
||||||
crd := crdutils.NewCustomResourceDefinition(crdutils.Config{
|
if _, err := pb.extensionsClient.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, strings.Join([]string{v1alpha1.PeerPlural, v1alpha1.GroupName}, "."), metav1.GetOptions{}); err != nil {
|
||||||
SpecDefinitionName: "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1.Peer",
|
return fmt.Errorf("CRD is not present: %v", err)
|
||||||
EnableValidation: true,
|
|
||||||
ResourceScope: string(v1beta1.ClusterScoped),
|
|
||||||
Group: v1alpha1.GroupName,
|
|
||||||
Kind: v1alpha1.PeerKind,
|
|
||||||
Version: v1alpha1.SchemeGroupVersion.Version,
|
|
||||||
Plural: v1alpha1.PeerPlural,
|
|
||||||
ShortNames: v1alpha1.PeerShortNames,
|
|
||||||
GetOpenAPIDefinitions: v1alpha1.GetOpenAPIDefinitions,
|
|
||||||
})
|
|
||||||
crd.Spec.Subresources.Scale = nil
|
|
||||||
crd.Spec.Subresources.Status = nil
|
|
||||||
|
|
||||||
_, err := pb.extensionsClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)
|
|
||||||
if err != nil && !apierrors.IsAlreadyExists(err) {
|
|
||||||
return fmt.Errorf("failed to create CRD: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go pb.informer.Run(stop)
|
go pb.informer.Run(ctx.Done())
|
||||||
if ok := cache.WaitForCacheSync(stop, func() bool {
|
if ok := cache.WaitForCacheSync(ctx.Done(), func() bool {
|
||||||
return pb.informer.HasSynced()
|
return pb.informer.HasSynced()
|
||||||
}); !ok {
|
}); !ok {
|
||||||
return errors.New("failed to sync peer cache")
|
return errors.New("failed to sync peer cache")
|
||||||
@@ -471,7 +512,7 @@ func (pb *peerBackend) List() ([]*mesh.Peer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set sets the fields of a peer.
|
// Set sets the fields of a peer.
|
||||||
func (pb *peerBackend) Set(name string, peer *mesh.Peer) error {
|
func (pb *peerBackend) Set(ctx context.Context, name string, peer *mesh.Peer) error {
|
||||||
old, err := pb.lister.Get(name)
|
old, err := pb.lister.Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to find peer: %v", err)
|
return fmt.Errorf("failed to find peer: %v", err)
|
||||||
@@ -482,22 +523,26 @@ func (pb *peerBackend) Set(name string, peer *mesh.Peer) error {
|
|||||||
p.Spec.AllowedIPs[i] = peer.AllowedIPs[i].String()
|
p.Spec.AllowedIPs[i] = peer.AllowedIPs[i].String()
|
||||||
}
|
}
|
||||||
if peer.Endpoint != nil {
|
if peer.Endpoint != nil {
|
||||||
var ip string
|
|
||||||
if peer.Endpoint.IP != nil {
|
|
||||||
ip = peer.Endpoint.IP.String()
|
|
||||||
}
|
|
||||||
p.Spec.Endpoint = &v1alpha1.PeerEndpoint{
|
p.Spec.Endpoint = &v1alpha1.PeerEndpoint{
|
||||||
DNSOrIP: v1alpha1.DNSOrIP{
|
DNSOrIP: v1alpha1.DNSOrIP{
|
||||||
IP: ip,
|
IP: peer.Endpoint.IP().String(),
|
||||||
DNS: peer.Endpoint.DNS,
|
DNS: peer.Endpoint.DNS(),
|
||||||
},
|
},
|
||||||
Port: peer.Endpoint.Port,
|
Port: uint32(peer.Endpoint.Port()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.Spec.PersistentKeepalive = peer.PersistentKeepalive
|
if peer.PersistentKeepaliveInterval == nil {
|
||||||
p.Spec.PresharedKey = string(peer.PresharedKey)
|
p.Spec.PersistentKeepalive = 0
|
||||||
p.Spec.PublicKey = string(peer.PublicKey)
|
} else {
|
||||||
if _, err = pb.client.KiloV1alpha1().Peers().Update(p); err != nil {
|
p.Spec.PersistentKeepalive = int(*peer.PersistentKeepaliveInterval / time.Second)
|
||||||
|
}
|
||||||
|
if peer.PresharedKey == nil {
|
||||||
|
p.Spec.PresharedKey = ""
|
||||||
|
} else {
|
||||||
|
p.Spec.PresharedKey = peer.PresharedKey.String()
|
||||||
|
}
|
||||||
|
p.Spec.PublicKey = peer.PublicKey.String()
|
||||||
|
if _, err = pb.client.KiloV1alpha1().Peers().Update(ctx, p, metav1.UpdateOptions{}); err != nil {
|
||||||
return fmt.Errorf("failed to update peer: %v", err)
|
return fmt.Errorf("failed to update peer: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -520,35 +565,3 @@ func normalizeIP(ip string) *net.IPNet {
|
|||||||
ipNet.IP = i.To16()
|
ipNet.IP = i.To16()
|
||||||
return ipNet
|
return ipNet
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseEndpoint(endpoint string) *wireguard.Endpoint {
|
|
||||||
if len(endpoint) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
parts := strings.Split(endpoint, ":")
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
portRaw := parts[len(parts)-1]
|
|
||||||
hostRaw := strings.Trim(strings.Join(parts[:len(parts)-1], ":"), "[]")
|
|
||||||
port, err := strconv.ParseUint(portRaw, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(validation.IsValidPortNum(int(port))) != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ip := net.ParseIP(hostRaw)
|
|
||||||
if ip == nil {
|
|
||||||
if len(validation.IsDNS1123Subdomain(hostRaw)) == 0 {
|
|
||||||
return &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{DNS: hostRaw}, Port: uint32(port)}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
|
||||||
ip = ip4
|
|
||||||
} else {
|
|
||||||
ip = ip.To16()
|
|
||||||
}
|
|
||||||
return &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: ip}, Port: uint32(port)}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2019 the Kilo authors
|
// Copyright 2021 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -17,8 +17,10 @@ package k8s
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/kylelemons/godebug/pretty"
|
"github.com/kylelemons/godebug/pretty"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
"github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
"github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||||
@@ -26,6 +28,30 @@ import (
|
|||||||
"github.com/squat/kilo/pkg/wireguard"
|
"github.com/squat/kilo/pkg/wireguard"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func mustKey() (k wgtypes.Key) {
|
||||||
|
var err error
|
||||||
|
if k, err = wgtypes.GeneratePrivateKey(); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustPSKKey() (key *wgtypes.Key) {
|
||||||
|
if k, err := wgtypes.GenerateKey(); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
} else {
|
||||||
|
key = &k
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
fooKey = mustKey()
|
||||||
|
pskKey = mustPSKKey()
|
||||||
|
second = time.Second
|
||||||
|
zero = time.Duration(0)
|
||||||
|
)
|
||||||
|
|
||||||
func TestTranslateNode(t *testing.T) {
|
func TestTranslateNode(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
@@ -54,8 +80,19 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
internalIPAnnotationKey: "10.0.0.2/32",
|
internalIPAnnotationKey: "10.0.0.2/32",
|
||||||
},
|
},
|
||||||
out: &mesh.Node{
|
out: &mesh.Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: net.ParseIP("10.0.0.1")}, Port: mesh.DefaultKiloPort},
|
Endpoint: wireguard.NewEndpoint(net.ParseIP("10.0.0.1").To4(), mesh.DefaultKiloPort),
|
||||||
InternalIP: &net.IPNet{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(32, 32)},
|
InternalIP: &net.IPNet{IP: net.ParseIP("10.0.0.2").To4(), Mask: net.CIDRMask(32, 32)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid ips with ipv6",
|
||||||
|
annotations: map[string]string{
|
||||||
|
endpointAnnotationKey: "[ff10::10]:51820",
|
||||||
|
internalIPAnnotationKey: "ff60::10/64",
|
||||||
|
},
|
||||||
|
out: &mesh.Node{
|
||||||
|
Endpoint: wireguard.NewEndpoint(net.ParseIP("ff10::10").To16(), mesh.DefaultKiloPort),
|
||||||
|
InternalIP: &net.IPNet{IP: net.ParseIP("ff60::10").To16(), Mask: net.CIDRMask(64, 128)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -68,7 +105,7 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
name: "normalize subnet",
|
name: "normalize subnet",
|
||||||
annotations: map[string]string{},
|
annotations: map[string]string{},
|
||||||
out: &mesh.Node{
|
out: &mesh.Node{
|
||||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(24, 32)},
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0").To4(), Mask: net.CIDRMask(24, 32)},
|
||||||
},
|
},
|
||||||
subnet: "10.2.0.1/24",
|
subnet: "10.2.0.1/24",
|
||||||
},
|
},
|
||||||
@@ -76,7 +113,7 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
name: "valid subnet",
|
name: "valid subnet",
|
||||||
annotations: map[string]string{},
|
annotations: map[string]string{},
|
||||||
out: &mesh.Node{
|
out: &mesh.Node{
|
||||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0").To4(), Mask: net.CIDRMask(24, 32)},
|
||||||
},
|
},
|
||||||
subnet: "10.2.1.0/24",
|
subnet: "10.2.1.0/24",
|
||||||
},
|
},
|
||||||
@@ -108,7 +145,7 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
forceEndpointAnnotationKey: "-10.0.0.2:51821",
|
forceEndpointAnnotationKey: "-10.0.0.2:51821",
|
||||||
},
|
},
|
||||||
out: &mesh.Node{
|
out: &mesh.Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: net.ParseIP("10.0.0.1")}, Port: mesh.DefaultKiloPort},
|
Endpoint: wireguard.NewEndpoint(net.ParseIP("10.0.0.1").To4(), mesh.DefaultKiloPort),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -118,7 +155,7 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
forceEndpointAnnotationKey: "10.0.0.2:51821",
|
forceEndpointAnnotationKey: "10.0.0.2:51821",
|
||||||
},
|
},
|
||||||
out: &mesh.Node{
|
out: &mesh.Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: net.ParseIP("10.0.0.2")}, Port: 51821},
|
Endpoint: wireguard.NewEndpoint(net.ParseIP("10.0.0.2").To4(), 51821),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -127,7 +164,7 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
persistentKeepaliveKey: "25",
|
persistentKeepaliveKey: "25",
|
||||||
},
|
},
|
||||||
out: &mesh.Node{
|
out: &mesh.Node{
|
||||||
PersistentKeepalive: 25,
|
PersistentKeepalive: 25 * time.Second,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -137,7 +174,7 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
forceInternalIPAnnotationKey: "-10.1.0.2/24",
|
forceInternalIPAnnotationKey: "-10.1.0.2/24",
|
||||||
},
|
},
|
||||||
out: &mesh.Node{
|
out: &mesh.Node{
|
||||||
InternalIP: &net.IPNet{IP: net.ParseIP("10.1.0.1"), Mask: net.CIDRMask(24, 32)},
|
InternalIP: &net.IPNet{IP: net.ParseIP("10.1.0.1").To4(), Mask: net.CIDRMask(24, 32)},
|
||||||
NoInternalIP: false,
|
NoInternalIP: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -148,7 +185,7 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
forceInternalIPAnnotationKey: "10.1.0.2/24",
|
forceInternalIPAnnotationKey: "10.1.0.2/24",
|
||||||
},
|
},
|
||||||
out: &mesh.Node{
|
out: &mesh.Node{
|
||||||
InternalIP: &net.IPNet{IP: net.ParseIP("10.1.0.2"), Mask: net.CIDRMask(24, 32)},
|
InternalIP: &net.IPNet{IP: net.ParseIP("10.1.0.2").To4(), Mask: net.CIDRMask(24, 32)},
|
||||||
NoInternalIP: false,
|
NoInternalIP: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -166,7 +203,7 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
forceEndpointAnnotationKey: "10.0.0.2:51821",
|
forceEndpointAnnotationKey: "10.0.0.2:51821",
|
||||||
forceInternalIPAnnotationKey: "10.1.0.2/32",
|
forceInternalIPAnnotationKey: "10.1.0.2/32",
|
||||||
internalIPAnnotationKey: "10.1.0.1/32",
|
internalIPAnnotationKey: "10.1.0.1/32",
|
||||||
keyAnnotationKey: "foo",
|
keyAnnotationKey: fooKey.String(),
|
||||||
lastSeenAnnotationKey: "1000000000",
|
lastSeenAnnotationKey: "1000000000",
|
||||||
leaderAnnotationKey: "",
|
leaderAnnotationKey: "",
|
||||||
locationAnnotationKey: "b",
|
locationAnnotationKey: "b",
|
||||||
@@ -177,14 +214,45 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
RegionLabelKey: "a",
|
RegionLabelKey: "a",
|
||||||
},
|
},
|
||||||
out: &mesh.Node{
|
out: &mesh.Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: net.ParseIP("10.0.0.2")}, Port: 51821},
|
Endpoint: wireguard.NewEndpoint(net.ParseIP("10.0.0.2").To4(), 51821),
|
||||||
NoInternalIP: false,
|
NoInternalIP: false,
|
||||||
InternalIP: &net.IPNet{IP: net.ParseIP("10.1.0.2"), Mask: net.CIDRMask(32, 32)},
|
InternalIP: &net.IPNet{IP: net.ParseIP("10.1.0.2").To4(), Mask: net.CIDRMask(32, 32)},
|
||||||
Key: []byte("foo"),
|
Key: fooKey,
|
||||||
LastSeen: 1000000000,
|
LastSeen: 1000000000,
|
||||||
Leader: true,
|
Leader: true,
|
||||||
Location: "b",
|
Location: "b",
|
||||||
PersistentKeepalive: 25,
|
PersistentKeepalive: 25 * time.Second,
|
||||||
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0").To4(), Mask: net.CIDRMask(24, 32)},
|
||||||
|
WireGuardIP: &net.IPNet{IP: net.ParseIP("10.4.0.1").To4(), Mask: net.CIDRMask(16, 32)},
|
||||||
|
},
|
||||||
|
subnet: "10.2.1.0/24",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "complete with ipv6",
|
||||||
|
annotations: map[string]string{
|
||||||
|
endpointAnnotationKey: "10.0.0.1:51820",
|
||||||
|
forceEndpointAnnotationKey: "[1100::10]:51821",
|
||||||
|
forceInternalIPAnnotationKey: "10.1.0.2/32",
|
||||||
|
internalIPAnnotationKey: "10.1.0.1/32",
|
||||||
|
keyAnnotationKey: fooKey.String(),
|
||||||
|
lastSeenAnnotationKey: "1000000000",
|
||||||
|
leaderAnnotationKey: "",
|
||||||
|
locationAnnotationKey: "b",
|
||||||
|
persistentKeepaliveKey: "25",
|
||||||
|
wireGuardIPAnnotationKey: "10.4.0.1/16",
|
||||||
|
},
|
||||||
|
labels: map[string]string{
|
||||||
|
RegionLabelKey: "a",
|
||||||
|
},
|
||||||
|
out: &mesh.Node{
|
||||||
|
Endpoint: wireguard.NewEndpoint(net.ParseIP("1100::10"), 51821),
|
||||||
|
NoInternalIP: false,
|
||||||
|
InternalIP: &net.IPNet{IP: net.ParseIP("10.1.0.2"), Mask: net.CIDRMask(32, 32)},
|
||||||
|
Key: fooKey,
|
||||||
|
LastSeen: 1000000000,
|
||||||
|
Leader: true,
|
||||||
|
Location: "b",
|
||||||
|
PersistentKeepalive: 25 * time.Second,
|
||||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
|
||||||
WireGuardIP: &net.IPNet{IP: net.ParseIP("10.4.0.1"), Mask: net.CIDRMask(16, 32)},
|
WireGuardIP: &net.IPNet{IP: net.ParseIP("10.4.0.1"), Mask: net.CIDRMask(16, 32)},
|
||||||
},
|
},
|
||||||
@@ -195,7 +263,7 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
annotations: map[string]string{
|
annotations: map[string]string{
|
||||||
endpointAnnotationKey: "10.0.0.1:51820",
|
endpointAnnotationKey: "10.0.0.1:51820",
|
||||||
internalIPAnnotationKey: "",
|
internalIPAnnotationKey: "",
|
||||||
keyAnnotationKey: "foo",
|
keyAnnotationKey: fooKey.String(),
|
||||||
lastSeenAnnotationKey: "1000000000",
|
lastSeenAnnotationKey: "1000000000",
|
||||||
locationAnnotationKey: "b",
|
locationAnnotationKey: "b",
|
||||||
persistentKeepaliveKey: "25",
|
persistentKeepaliveKey: "25",
|
||||||
@@ -205,13 +273,13 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
RegionLabelKey: "a",
|
RegionLabelKey: "a",
|
||||||
},
|
},
|
||||||
out: &mesh.Node{
|
out: &mesh.Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: net.ParseIP("10.0.0.1")}, Port: 51820},
|
Endpoint: wireguard.NewEndpoint(net.ParseIP("10.0.0.1"), 51820),
|
||||||
InternalIP: nil,
|
InternalIP: nil,
|
||||||
Key: []byte("foo"),
|
Key: fooKey,
|
||||||
LastSeen: 1000000000,
|
LastSeen: 1000000000,
|
||||||
Leader: false,
|
Leader: false,
|
||||||
Location: "b",
|
Location: "b",
|
||||||
PersistentKeepalive: 25,
|
PersistentKeepalive: 25 * time.Second,
|
||||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
|
||||||
WireGuardIP: &net.IPNet{IP: net.ParseIP("10.4.0.1"), Mask: net.CIDRMask(16, 32)},
|
WireGuardIP: &net.IPNet{IP: net.ParseIP("10.4.0.1"), Mask: net.CIDRMask(16, 32)},
|
||||||
},
|
},
|
||||||
@@ -223,7 +291,7 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
endpointAnnotationKey: "10.0.0.1:51820",
|
endpointAnnotationKey: "10.0.0.1:51820",
|
||||||
internalIPAnnotationKey: "10.1.0.1/32",
|
internalIPAnnotationKey: "10.1.0.1/32",
|
||||||
forceInternalIPAnnotationKey: "",
|
forceInternalIPAnnotationKey: "",
|
||||||
keyAnnotationKey: "foo",
|
keyAnnotationKey: fooKey.String(),
|
||||||
lastSeenAnnotationKey: "1000000000",
|
lastSeenAnnotationKey: "1000000000",
|
||||||
locationAnnotationKey: "b",
|
locationAnnotationKey: "b",
|
||||||
persistentKeepaliveKey: "25",
|
persistentKeepaliveKey: "25",
|
||||||
@@ -233,14 +301,14 @@ func TestTranslateNode(t *testing.T) {
|
|||||||
RegionLabelKey: "a",
|
RegionLabelKey: "a",
|
||||||
},
|
},
|
||||||
out: &mesh.Node{
|
out: &mesh.Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: net.ParseIP("10.0.0.1")}, Port: 51820},
|
Endpoint: wireguard.NewEndpoint(net.ParseIP("10.0.0.1"), 51820),
|
||||||
NoInternalIP: true,
|
NoInternalIP: true,
|
||||||
InternalIP: nil,
|
InternalIP: nil,
|
||||||
Key: []byte("foo"),
|
Key: fooKey,
|
||||||
LastSeen: 1000000000,
|
LastSeen: 1000000000,
|
||||||
Leader: false,
|
Leader: false,
|
||||||
Location: "b",
|
Location: "b",
|
||||||
PersistentKeepalive: 25,
|
PersistentKeepalive: 25 * time.Second,
|
||||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
|
||||||
WireGuardIP: &net.IPNet{IP: net.ParseIP("10.4.0.1"), Mask: net.CIDRMask(16, 32)},
|
WireGuardIP: &net.IPNet{IP: net.ParseIP("10.4.0.1"), Mask: net.CIDRMask(16, 32)},
|
||||||
},
|
},
|
||||||
@@ -266,7 +334,13 @@ func TestTranslatePeer(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty",
|
name: "empty",
|
||||||
out: &mesh.Peer{},
|
out: &mesh.Peer{
|
||||||
|
Peer: wireguard.Peer{
|
||||||
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
|
PersistentKeepaliveInterval: &zero,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid ips",
|
name: "invalid ips",
|
||||||
@@ -276,7 +350,13 @@ func TestTranslatePeer(t *testing.T) {
|
|||||||
"foo",
|
"foo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
out: &mesh.Peer{},
|
out: &mesh.Peer{
|
||||||
|
Peer: wireguard.Peer{
|
||||||
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
|
PersistentKeepaliveInterval: &zero,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid ips",
|
name: "valid ips",
|
||||||
@@ -288,9 +368,12 @@ func TestTranslatePeer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
out: &mesh.Peer{
|
out: &mesh.Peer{
|
||||||
Peer: wireguard.Peer{
|
Peer: wireguard.Peer{
|
||||||
AllowedIPs: []*net.IPNet{
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
{IP: net.ParseIP("10.0.0.1"), Mask: net.CIDRMask(24, 32)},
|
AllowedIPs: []net.IPNet{
|
||||||
{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(32, 32)},
|
{IP: net.ParseIP("10.0.0.1"), Mask: net.CIDRMask(24, 32)},
|
||||||
|
{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(32, 32)},
|
||||||
|
},
|
||||||
|
PersistentKeepaliveInterval: &zero,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -305,7 +388,13 @@ func TestTranslatePeer(t *testing.T) {
|
|||||||
Port: mesh.DefaultKiloPort,
|
Port: mesh.DefaultKiloPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
out: &mesh.Peer{},
|
out: &mesh.Peer{
|
||||||
|
Peer: wireguard.Peer{
|
||||||
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
|
PersistentKeepaliveInterval: &zero,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "only endpoint port",
|
name: "only endpoint port",
|
||||||
@@ -314,7 +403,13 @@ func TestTranslatePeer(t *testing.T) {
|
|||||||
Port: mesh.DefaultKiloPort,
|
Port: mesh.DefaultKiloPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
out: &mesh.Peer{},
|
out: &mesh.Peer{
|
||||||
|
Peer: wireguard.Peer{
|
||||||
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
|
PersistentKeepaliveInterval: &zero,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid endpoint ip",
|
name: "valid endpoint ip",
|
||||||
@@ -328,10 +423,29 @@ func TestTranslatePeer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
out: &mesh.Peer{
|
out: &mesh.Peer{
|
||||||
Peer: wireguard.Peer{
|
Peer: wireguard.Peer{
|
||||||
Endpoint: &wireguard.Endpoint{
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
DNSOrIP: wireguard.DNSOrIP{IP: net.ParseIP("10.0.0.1")},
|
PersistentKeepaliveInterval: &zero,
|
||||||
Port: mesh.DefaultKiloPort,
|
|
||||||
},
|
},
|
||||||
|
Endpoint: wireguard.NewEndpoint(net.ParseIP("10.0.0.1").To4(), mesh.DefaultKiloPort),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid endpoint ipv6",
|
||||||
|
spec: v1alpha1.PeerSpec{
|
||||||
|
Endpoint: &v1alpha1.PeerEndpoint{
|
||||||
|
DNSOrIP: v1alpha1.DNSOrIP{
|
||||||
|
IP: "ff60::2",
|
||||||
|
},
|
||||||
|
Port: mesh.DefaultKiloPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: &mesh.Peer{
|
||||||
|
Peer: wireguard.Peer{
|
||||||
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
|
PersistentKeepaliveInterval: &zero,
|
||||||
|
},
|
||||||
|
Endpoint: wireguard.NewEndpoint(net.ParseIP("ff60::2").To16(), mesh.DefaultKiloPort),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -347,9 +461,9 @@ func TestTranslatePeer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
out: &mesh.Peer{
|
out: &mesh.Peer{
|
||||||
Peer: wireguard.Peer{
|
Peer: wireguard.Peer{
|
||||||
Endpoint: &wireguard.Endpoint{
|
Endpoint: wireguard.ParseEndpoint("example.com:51820"),
|
||||||
DNSOrIP: wireguard.DNSOrIP{DNS: "example.com"},
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
Port: mesh.DefaultKiloPort,
|
PersistentKeepaliveInterval: &zero,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -359,16 +473,25 @@ func TestTranslatePeer(t *testing.T) {
|
|||||||
spec: v1alpha1.PeerSpec{
|
spec: v1alpha1.PeerSpec{
|
||||||
PublicKey: "",
|
PublicKey: "",
|
||||||
},
|
},
|
||||||
out: &mesh.Peer{},
|
out: &mesh.Peer{
|
||||||
|
Peer: wireguard.Peer{
|
||||||
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
|
PersistentKeepaliveInterval: &zero,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid key",
|
name: "valid key",
|
||||||
spec: v1alpha1.PeerSpec{
|
spec: v1alpha1.PeerSpec{
|
||||||
PublicKey: "foo",
|
PublicKey: fooKey.String(),
|
||||||
},
|
},
|
||||||
out: &mesh.Peer{
|
out: &mesh.Peer{
|
||||||
Peer: wireguard.Peer{
|
Peer: wireguard.Peer{
|
||||||
PublicKey: []byte("foo"),
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
|
PublicKey: fooKey,
|
||||||
|
PersistentKeepaliveInterval: &zero,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -377,7 +500,13 @@ func TestTranslatePeer(t *testing.T) {
|
|||||||
spec: v1alpha1.PeerSpec{
|
spec: v1alpha1.PeerSpec{
|
||||||
PersistentKeepalive: -1,
|
PersistentKeepalive: -1,
|
||||||
},
|
},
|
||||||
out: &mesh.Peer{},
|
out: &mesh.Peer{
|
||||||
|
Peer: wireguard.Peer{
|
||||||
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
|
PersistentKeepaliveInterval: &zero,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid keepalive",
|
name: "valid keepalive",
|
||||||
@@ -386,18 +515,23 @@ func TestTranslatePeer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
out: &mesh.Peer{
|
out: &mesh.Peer{
|
||||||
Peer: wireguard.Peer{
|
Peer: wireguard.Peer{
|
||||||
PersistentKeepalive: 1,
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
|
PersistentKeepaliveInterval: &second,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid preshared key",
|
name: "valid preshared key",
|
||||||
spec: v1alpha1.PeerSpec{
|
spec: v1alpha1.PeerSpec{
|
||||||
PresharedKey: "psk",
|
PresharedKey: pskKey.String(),
|
||||||
},
|
},
|
||||||
out: &mesh.Peer{
|
out: &mesh.Peer{
|
||||||
Peer: wireguard.Peer{
|
Peer: wireguard.Peer{
|
||||||
PresharedKey: []byte("psk"),
|
PeerConfig: wgtypes.PeerConfig{
|
||||||
|
PersistentKeepaliveInterval: &zero,
|
||||||
|
PresharedKey: pskKey,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -410,52 +544,3 @@ func TestTranslatePeer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseEndpoint(t *testing.T) {
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
endpoint string
|
|
||||||
out *wireguard.Endpoint
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
endpoint: "",
|
|
||||||
out: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid IP",
|
|
||||||
endpoint: "10.0.0.:51820",
|
|
||||||
out: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid hostname",
|
|
||||||
endpoint: "foo-:51820",
|
|
||||||
out: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid port",
|
|
||||||
endpoint: "10.0.0.1:100000000",
|
|
||||||
out: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid IP",
|
|
||||||
endpoint: "10.0.0.1:51820",
|
|
||||||
out: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: net.ParseIP("10.0.0.1")}, Port: mesh.DefaultKiloPort},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid IPv6",
|
|
||||||
endpoint: "[ff02::114]:51820",
|
|
||||||
out: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: net.ParseIP("ff02::114")}, Port: mesh.DefaultKiloPort},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid hostname",
|
|
||||||
endpoint: "foo:51821",
|
|
||||||
out: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{DNS: "foo"}, Port: 51821},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
endpoint := parseEndpoint(tc.endpoint)
|
|
||||||
if diff := pretty.Compare(endpoint, tc.out); diff != "" {
|
|
||||||
t.Errorf("test case %q: got diff: %v", tc.name, diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -17,6 +17,9 @@
|
|||||||
package versioned
|
package versioned
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
kilov1alpha1 "github.com/squat/kilo/pkg/k8s/clientset/versioned/typed/kilo/v1alpha1"
|
kilov1alpha1 "github.com/squat/kilo/pkg/k8s/clientset/versioned/typed/kilo/v1alpha1"
|
||||||
discovery "k8s.io/client-go/discovery"
|
discovery "k8s.io/client-go/discovery"
|
||||||
rest "k8s.io/client-go/rest"
|
rest "k8s.io/client-go/rest"
|
||||||
@@ -49,19 +52,47 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewForConfig creates a new Clientset for the given config.
|
// NewForConfig creates a new Clientset for the given config.
|
||||||
|
// If config's RateLimiter is not set and QPS and Burst are acceptable,
|
||||||
|
// NewForConfig will generate a rate-limiter in configShallowCopy.
|
||||||
|
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
|
||||||
|
// where httpClient was generated with rest.HTTPClientFor(c).
|
||||||
func NewForConfig(c *rest.Config) (*Clientset, error) {
|
func NewForConfig(c *rest.Config) (*Clientset, error) {
|
||||||
configShallowCopy := *c
|
configShallowCopy := *c
|
||||||
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
|
|
||||||
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
|
if configShallowCopy.UserAgent == "" {
|
||||||
|
configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||||
}
|
}
|
||||||
var cs Clientset
|
|
||||||
var err error
|
// share the transport between all clients
|
||||||
cs.kiloV1alpha1, err = kilov1alpha1.NewForConfig(&configShallowCopy)
|
httpClient, err := rest.HTTPClientFor(&configShallowCopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
|
return NewForConfigAndClient(&configShallowCopy, httpClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewForConfigAndClient creates a new Clientset for the given config and http client.
|
||||||
|
// Note the http client provided takes precedence over the configured transport values.
|
||||||
|
// If config's RateLimiter is not set and QPS and Burst are acceptable,
|
||||||
|
// NewForConfigAndClient will generate a rate-limiter in configShallowCopy.
|
||||||
|
func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {
|
||||||
|
configShallowCopy := *c
|
||||||
|
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
|
||||||
|
if configShallowCopy.Burst <= 0 {
|
||||||
|
return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
|
||||||
|
}
|
||||||
|
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs Clientset
|
||||||
|
var err error
|
||||||
|
cs.kiloV1alpha1, err = kilov1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -71,11 +102,11 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
|
|||||||
// NewForConfigOrDie creates a new Clientset for the given config and
|
// NewForConfigOrDie creates a new Clientset for the given config and
|
||||||
// panics if there is an error in the config.
|
// panics if there is an error in the config.
|
||||||
func NewForConfigOrDie(c *rest.Config) *Clientset {
|
func NewForConfigOrDie(c *rest.Config) *Clientset {
|
||||||
var cs Clientset
|
cs, err := NewForConfig(c)
|
||||||
cs.kiloV1alpha1 = kilov1alpha1.NewForConfigOrDie(c)
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
|
}
|
||||||
return &cs
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Clientset for the given RESTClient.
|
// New creates a new Clientset for the given RESTClient.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -39,7 +39,7 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := &Clientset{}
|
cs := &Clientset{tracker: o}
|
||||||
cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
|
cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
|
||||||
cs.AddReactor("*", "*", testing.ObjectReaction(o))
|
cs.AddReactor("*", "*", testing.ObjectReaction(o))
|
||||||
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
|
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
|
||||||
@@ -61,13 +61,21 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset {
|
|||||||
type Clientset struct {
|
type Clientset struct {
|
||||||
testing.Fake
|
testing.Fake
|
||||||
discovery *fakediscovery.FakeDiscovery
|
discovery *fakediscovery.FakeDiscovery
|
||||||
|
tracker testing.ObjectTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
||||||
return c.discovery
|
return c.discovery
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ clientset.Interface = &Clientset{}
|
func (c *Clientset) Tracker() testing.ObjectTracker {
|
||||||
|
return c.tracker
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ clientset.Interface = &Clientset{}
|
||||||
|
_ testing.FakeClient = &Clientset{}
|
||||||
|
)
|
||||||
|
|
||||||
// KiloV1alpha1 retrieves the KiloV1alpha1Client
|
// KiloV1alpha1 retrieves the KiloV1alpha1Client
|
||||||
func (c *Clientset) KiloV1alpha1() kilov1alpha1.KiloV1alpha1Interface {
|
func (c *Clientset) KiloV1alpha1() kilov1alpha1.KiloV1alpha1Interface {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
|
|
||||||
var scheme = runtime.NewScheme()
|
var scheme = runtime.NewScheme()
|
||||||
var codecs = serializer.NewCodecFactory(scheme)
|
var codecs = serializer.NewCodecFactory(scheme)
|
||||||
var parameterCodec = runtime.NewParameterCodec(scheme)
|
|
||||||
var localSchemeBuilder = runtime.SchemeBuilder{
|
var localSchemeBuilder = runtime.SchemeBuilder{
|
||||||
kilov1alpha1.AddToScheme,
|
kilov1alpha1.AddToScheme,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -17,6 +17,8 @@
|
|||||||
package fake
|
package fake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
v1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
v1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
labels "k8s.io/apimachinery/pkg/labels"
|
labels "k8s.io/apimachinery/pkg/labels"
|
||||||
@@ -31,12 +33,12 @@ type FakePeers struct {
|
|||||||
Fake *FakeKiloV1alpha1
|
Fake *FakeKiloV1alpha1
|
||||||
}
|
}
|
||||||
|
|
||||||
var peersResource = schema.GroupVersionResource{Group: "kilo", Version: "v1alpha1", Resource: "peers"}
|
var peersResource = schema.GroupVersionResource{Group: "kilo.squat.ai", Version: "v1alpha1", Resource: "peers"}
|
||||||
|
|
||||||
var peersKind = schema.GroupVersionKind{Group: "kilo", Version: "v1alpha1", Kind: "Peer"}
|
var peersKind = schema.GroupVersionKind{Group: "kilo.squat.ai", Version: "v1alpha1", Kind: "Peer"}
|
||||||
|
|
||||||
// Get takes name of the peer, and returns the corresponding peer object, and an error if there is any.
|
// Get takes name of the peer, and returns the corresponding peer object, and an error if there is any.
|
||||||
func (c *FakePeers) Get(name string, options v1.GetOptions) (result *v1alpha1.Peer, err error) {
|
func (c *FakePeers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Peer, err error) {
|
||||||
obj, err := c.Fake.
|
obj, err := c.Fake.
|
||||||
Invokes(testing.NewRootGetAction(peersResource, name), &v1alpha1.Peer{})
|
Invokes(testing.NewRootGetAction(peersResource, name), &v1alpha1.Peer{})
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
@@ -46,7 +48,7 @@ func (c *FakePeers) Get(name string, options v1.GetOptions) (result *v1alpha1.Pe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List takes label and field selectors, and returns the list of Peers that match those selectors.
|
// List takes label and field selectors, and returns the list of Peers that match those selectors.
|
||||||
func (c *FakePeers) List(opts v1.ListOptions) (result *v1alpha1.PeerList, err error) {
|
func (c *FakePeers) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.PeerList, err error) {
|
||||||
obj, err := c.Fake.
|
obj, err := c.Fake.
|
||||||
Invokes(testing.NewRootListAction(peersResource, peersKind, opts), &v1alpha1.PeerList{})
|
Invokes(testing.NewRootListAction(peersResource, peersKind, opts), &v1alpha1.PeerList{})
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
@@ -67,13 +69,13 @@ func (c *FakePeers) List(opts v1.ListOptions) (result *v1alpha1.PeerList, err er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Watch returns a watch.Interface that watches the requested peers.
|
// Watch returns a watch.Interface that watches the requested peers.
|
||||||
func (c *FakePeers) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
func (c *FakePeers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
|
||||||
return c.Fake.
|
return c.Fake.
|
||||||
InvokesWatch(testing.NewRootWatchAction(peersResource, opts))
|
InvokesWatch(testing.NewRootWatchAction(peersResource, opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create takes the representation of a peer and creates it. Returns the server's representation of the peer, and an error, if there is any.
|
// Create takes the representation of a peer and creates it. Returns the server's representation of the peer, and an error, if there is any.
|
||||||
func (c *FakePeers) Create(peer *v1alpha1.Peer) (result *v1alpha1.Peer, err error) {
|
func (c *FakePeers) Create(ctx context.Context, peer *v1alpha1.Peer, opts v1.CreateOptions) (result *v1alpha1.Peer, err error) {
|
||||||
obj, err := c.Fake.
|
obj, err := c.Fake.
|
||||||
Invokes(testing.NewRootCreateAction(peersResource, peer), &v1alpha1.Peer{})
|
Invokes(testing.NewRootCreateAction(peersResource, peer), &v1alpha1.Peer{})
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
@@ -83,7 +85,7 @@ func (c *FakePeers) Create(peer *v1alpha1.Peer) (result *v1alpha1.Peer, err erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update takes the representation of a peer and updates it. Returns the server's representation of the peer, and an error, if there is any.
|
// Update takes the representation of a peer and updates it. Returns the server's representation of the peer, and an error, if there is any.
|
||||||
func (c *FakePeers) Update(peer *v1alpha1.Peer) (result *v1alpha1.Peer, err error) {
|
func (c *FakePeers) Update(ctx context.Context, peer *v1alpha1.Peer, opts v1.UpdateOptions) (result *v1alpha1.Peer, err error) {
|
||||||
obj, err := c.Fake.
|
obj, err := c.Fake.
|
||||||
Invokes(testing.NewRootUpdateAction(peersResource, peer), &v1alpha1.Peer{})
|
Invokes(testing.NewRootUpdateAction(peersResource, peer), &v1alpha1.Peer{})
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
@@ -93,22 +95,22 @@ func (c *FakePeers) Update(peer *v1alpha1.Peer) (result *v1alpha1.Peer, err erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete takes name of the peer and deletes it. Returns an error if one occurs.
|
// Delete takes name of the peer and deletes it. Returns an error if one occurs.
|
||||||
func (c *FakePeers) Delete(name string, options *v1.DeleteOptions) error {
|
func (c *FakePeers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||||
_, err := c.Fake.
|
_, err := c.Fake.
|
||||||
Invokes(testing.NewRootDeleteAction(peersResource, name), &v1alpha1.Peer{})
|
Invokes(testing.NewRootDeleteActionWithOptions(peersResource, name, opts), &v1alpha1.Peer{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCollection deletes a collection of objects.
|
// DeleteCollection deletes a collection of objects.
|
||||||
func (c *FakePeers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
func (c *FakePeers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
|
||||||
action := testing.NewRootDeleteCollectionAction(peersResource, listOptions)
|
action := testing.NewRootDeleteCollectionAction(peersResource, listOpts)
|
||||||
|
|
||||||
_, err := c.Fake.Invokes(action, &v1alpha1.PeerList{})
|
_, err := c.Fake.Invokes(action, &v1alpha1.PeerList{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch applies the patch and returns the patched peer.
|
// Patch applies the patch and returns the patched peer.
|
||||||
func (c *FakePeers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Peer, err error) {
|
func (c *FakePeers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Peer, err error) {
|
||||||
obj, err := c.Fake.
|
obj, err := c.Fake.
|
||||||
Invokes(testing.NewRootPatchSubresourceAction(peersResource, name, pt, data, subresources...), &v1alpha1.Peer{})
|
Invokes(testing.NewRootPatchSubresourceAction(peersResource, name, pt, data, subresources...), &v1alpha1.Peer{})
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -17,9 +17,10 @@
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
v1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
v1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||||
"github.com/squat/kilo/pkg/k8s/clientset/versioned/scheme"
|
"github.com/squat/kilo/pkg/k8s/clientset/versioned/scheme"
|
||||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
|
||||||
rest "k8s.io/client-go/rest"
|
rest "k8s.io/client-go/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ type KiloV1alpha1Interface interface {
|
|||||||
PeersGetter
|
PeersGetter
|
||||||
}
|
}
|
||||||
|
|
||||||
// KiloV1alpha1Client is used to interact with features provided by the kilo group.
|
// KiloV1alpha1Client is used to interact with features provided by the kilo.squat.ai group.
|
||||||
type KiloV1alpha1Client struct {
|
type KiloV1alpha1Client struct {
|
||||||
restClient rest.Interface
|
restClient rest.Interface
|
||||||
}
|
}
|
||||||
@@ -38,12 +39,28 @@ func (c *KiloV1alpha1Client) Peers() PeerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewForConfig creates a new KiloV1alpha1Client for the given config.
|
// NewForConfig creates a new KiloV1alpha1Client for the given config.
|
||||||
|
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
|
||||||
|
// where httpClient was generated with rest.HTTPClientFor(c).
|
||||||
func NewForConfig(c *rest.Config) (*KiloV1alpha1Client, error) {
|
func NewForConfig(c *rest.Config) (*KiloV1alpha1Client, error) {
|
||||||
config := *c
|
config := *c
|
||||||
if err := setConfigDefaults(&config); err != nil {
|
if err := setConfigDefaults(&config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
client, err := rest.RESTClientFor(&config)
|
httpClient, err := rest.HTTPClientFor(&config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewForConfigAndClient(&config, httpClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewForConfigAndClient creates a new KiloV1alpha1Client for the given config and http client.
|
||||||
|
// Note the http client provided takes precedence over the configured transport values.
|
||||||
|
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*KiloV1alpha1Client, error) {
|
||||||
|
config := *c
|
||||||
|
if err := setConfigDefaults(&config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client, err := rest.RESTClientForConfigAndClient(&config, h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -69,7 +86,7 @@ func setConfigDefaults(config *rest.Config) error {
|
|||||||
gv := v1alpha1.SchemeGroupVersion
|
gv := v1alpha1.SchemeGroupVersion
|
||||||
config.GroupVersion = &gv
|
config.GroupVersion = &gv
|
||||||
config.APIPath = "/apis"
|
config.APIPath = "/apis"
|
||||||
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
|
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
if config.UserAgent == "" {
|
if config.UserAgent == "" {
|
||||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
v1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||||
@@ -35,14 +36,14 @@ type PeersGetter interface {
|
|||||||
|
|
||||||
// PeerInterface has methods to work with Peer resources.
|
// PeerInterface has methods to work with Peer resources.
|
||||||
type PeerInterface interface {
|
type PeerInterface interface {
|
||||||
Create(*v1alpha1.Peer) (*v1alpha1.Peer, error)
|
Create(ctx context.Context, peer *v1alpha1.Peer, opts v1.CreateOptions) (*v1alpha1.Peer, error)
|
||||||
Update(*v1alpha1.Peer) (*v1alpha1.Peer, error)
|
Update(ctx context.Context, peer *v1alpha1.Peer, opts v1.UpdateOptions) (*v1alpha1.Peer, error)
|
||||||
Delete(name string, options *v1.DeleteOptions) error
|
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
|
||||||
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
|
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
|
||||||
Get(name string, options v1.GetOptions) (*v1alpha1.Peer, error)
|
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Peer, error)
|
||||||
List(opts v1.ListOptions) (*v1alpha1.PeerList, error)
|
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.PeerList, error)
|
||||||
Watch(opts v1.ListOptions) (watch.Interface, error)
|
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
|
||||||
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Peer, err error)
|
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Peer, err error)
|
||||||
PeerExpansion
|
PeerExpansion
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,19 +60,19 @@ func newPeers(c *KiloV1alpha1Client) *peers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get takes name of the peer, and returns the corresponding peer object, and an error if there is any.
|
// Get takes name of the peer, and returns the corresponding peer object, and an error if there is any.
|
||||||
func (c *peers) Get(name string, options v1.GetOptions) (result *v1alpha1.Peer, err error) {
|
func (c *peers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Peer, err error) {
|
||||||
result = &v1alpha1.Peer{}
|
result = &v1alpha1.Peer{}
|
||||||
err = c.client.Get().
|
err = c.client.Get().
|
||||||
Resource("peers").
|
Resource("peers").
|
||||||
Name(name).
|
Name(name).
|
||||||
VersionedParams(&options, scheme.ParameterCodec).
|
VersionedParams(&options, scheme.ParameterCodec).
|
||||||
Do().
|
Do(ctx).
|
||||||
Into(result)
|
Into(result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// List takes label and field selectors, and returns the list of Peers that match those selectors.
|
// List takes label and field selectors, and returns the list of Peers that match those selectors.
|
||||||
func (c *peers) List(opts v1.ListOptions) (result *v1alpha1.PeerList, err error) {
|
func (c *peers) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.PeerList, err error) {
|
||||||
var timeout time.Duration
|
var timeout time.Duration
|
||||||
if opts.TimeoutSeconds != nil {
|
if opts.TimeoutSeconds != nil {
|
||||||
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||||
@@ -81,13 +82,13 @@ func (c *peers) List(opts v1.ListOptions) (result *v1alpha1.PeerList, err error)
|
|||||||
Resource("peers").
|
Resource("peers").
|
||||||
VersionedParams(&opts, scheme.ParameterCodec).
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
Timeout(timeout).
|
Timeout(timeout).
|
||||||
Do().
|
Do(ctx).
|
||||||
Into(result)
|
Into(result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch returns a watch.Interface that watches the requested peers.
|
// Watch returns a watch.Interface that watches the requested peers.
|
||||||
func (c *peers) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
func (c *peers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
|
||||||
var timeout time.Duration
|
var timeout time.Duration
|
||||||
if opts.TimeoutSeconds != nil {
|
if opts.TimeoutSeconds != nil {
|
||||||
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||||
@@ -97,66 +98,69 @@ func (c *peers) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
|||||||
Resource("peers").
|
Resource("peers").
|
||||||
VersionedParams(&opts, scheme.ParameterCodec).
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
Timeout(timeout).
|
Timeout(timeout).
|
||||||
Watch()
|
Watch(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create takes the representation of a peer and creates it. Returns the server's representation of the peer, and an error, if there is any.
|
// Create takes the representation of a peer and creates it. Returns the server's representation of the peer, and an error, if there is any.
|
||||||
func (c *peers) Create(peer *v1alpha1.Peer) (result *v1alpha1.Peer, err error) {
|
func (c *peers) Create(ctx context.Context, peer *v1alpha1.Peer, opts v1.CreateOptions) (result *v1alpha1.Peer, err error) {
|
||||||
result = &v1alpha1.Peer{}
|
result = &v1alpha1.Peer{}
|
||||||
err = c.client.Post().
|
err = c.client.Post().
|
||||||
Resource("peers").
|
Resource("peers").
|
||||||
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
Body(peer).
|
Body(peer).
|
||||||
Do().
|
Do(ctx).
|
||||||
Into(result)
|
Into(result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update takes the representation of a peer and updates it. Returns the server's representation of the peer, and an error, if there is any.
|
// Update takes the representation of a peer and updates it. Returns the server's representation of the peer, and an error, if there is any.
|
||||||
func (c *peers) Update(peer *v1alpha1.Peer) (result *v1alpha1.Peer, err error) {
|
func (c *peers) Update(ctx context.Context, peer *v1alpha1.Peer, opts v1.UpdateOptions) (result *v1alpha1.Peer, err error) {
|
||||||
result = &v1alpha1.Peer{}
|
result = &v1alpha1.Peer{}
|
||||||
err = c.client.Put().
|
err = c.client.Put().
|
||||||
Resource("peers").
|
Resource("peers").
|
||||||
Name(peer.Name).
|
Name(peer.Name).
|
||||||
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
Body(peer).
|
Body(peer).
|
||||||
Do().
|
Do(ctx).
|
||||||
Into(result)
|
Into(result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete takes name of the peer and deletes it. Returns an error if one occurs.
|
// Delete takes name of the peer and deletes it. Returns an error if one occurs.
|
||||||
func (c *peers) Delete(name string, options *v1.DeleteOptions) error {
|
func (c *peers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||||
return c.client.Delete().
|
return c.client.Delete().
|
||||||
Resource("peers").
|
Resource("peers").
|
||||||
Name(name).
|
Name(name).
|
||||||
Body(options).
|
Body(&opts).
|
||||||
Do().
|
Do(ctx).
|
||||||
Error()
|
Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCollection deletes a collection of objects.
|
// DeleteCollection deletes a collection of objects.
|
||||||
func (c *peers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
func (c *peers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
|
||||||
var timeout time.Duration
|
var timeout time.Duration
|
||||||
if listOptions.TimeoutSeconds != nil {
|
if listOpts.TimeoutSeconds != nil {
|
||||||
timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
|
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
|
||||||
}
|
}
|
||||||
return c.client.Delete().
|
return c.client.Delete().
|
||||||
Resource("peers").
|
Resource("peers").
|
||||||
VersionedParams(&listOptions, scheme.ParameterCodec).
|
VersionedParams(&listOpts, scheme.ParameterCodec).
|
||||||
Timeout(timeout).
|
Timeout(timeout).
|
||||||
Body(options).
|
Body(&opts).
|
||||||
Do().
|
Do(ctx).
|
||||||
Error()
|
Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch applies the patch and returns the patched peer.
|
// Patch applies the patch and returns the patched peer.
|
||||||
func (c *peers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Peer, err error) {
|
func (c *peers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Peer, err error) {
|
||||||
result = &v1alpha1.Peer{}
|
result = &v1alpha1.Peer{}
|
||||||
err = c.client.Patch(pt).
|
err = c.client.Patch(pt).
|
||||||
Resource("peers").
|
Resource("peers").
|
||||||
SubResource(subresources...).
|
|
||||||
Name(name).
|
Name(name).
|
||||||
|
SubResource(subresources...).
|
||||||
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
Body(data).
|
Body(data).
|
||||||
Do().
|
Do(ctx).
|
||||||
Into(result)
|
Into(result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -50,7 +50,7 @@ func (f *genericInformer) Lister() cache.GenericLister {
|
|||||||
// TODO extend this to unknown resources with a client pool
|
// TODO extend this to unknown resources with a client pool
|
||||||
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
|
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
|
||||||
switch resource {
|
switch resource {
|
||||||
// Group=kilo, Version=v1alpha1
|
// Group=kilo.squat.ai, Version=v1alpha1
|
||||||
case v1alpha1.SchemeGroupVersion.WithResource("peers"):
|
case v1alpha1.SchemeGroupVersion.WithResource("peers"):
|
||||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Kilo().V1alpha1().Peers().Informer()}, nil
|
return &genericInformer{resource: resource.GroupResource(), informer: f.Kilo().V1alpha1().Peers().Informer()}, nil
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
time "time"
|
time "time"
|
||||||
|
|
||||||
kilov1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
kilov1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||||
@@ -58,13 +59,13 @@ func NewFilteredPeerInformer(client versioned.Interface, resyncPeriod time.Durat
|
|||||||
if tweakListOptions != nil {
|
if tweakListOptions != nil {
|
||||||
tweakListOptions(&options)
|
tweakListOptions(&options)
|
||||||
}
|
}
|
||||||
return client.KiloV1alpha1().Peers().List(options)
|
return client.KiloV1alpha1().Peers().List(context.TODO(), options)
|
||||||
},
|
},
|
||||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||||
if tweakListOptions != nil {
|
if tweakListOptions != nil {
|
||||||
tweakListOptions(&options)
|
tweakListOptions(&options)
|
||||||
}
|
}
|
||||||
return client.KiloV1alpha1().Peers().Watch(options)
|
return client.KiloV1alpha1().Peers().Watch(context.TODO(), options)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&kilov1alpha1.Peer{},
|
&kilov1alpha1.Peer{},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Kilo authors
|
// Copyright 2022 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -24,10 +24,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// PeerLister helps list Peers.
|
// PeerLister helps list Peers.
|
||||||
|
// All objects returned here must be treated as read-only.
|
||||||
type PeerLister interface {
|
type PeerLister interface {
|
||||||
// List lists all Peers in the indexer.
|
// List lists all Peers in the indexer.
|
||||||
|
// Objects returned here must be treated as read-only.
|
||||||
List(selector labels.Selector) (ret []*v1alpha1.Peer, err error)
|
List(selector labels.Selector) (ret []*v1alpha1.Peer, err error)
|
||||||
// Get retrieves the Peer from the index for a given name.
|
// Get retrieves the Peer from the index for a given name.
|
||||||
|
// Objects returned here must be treated as read-only.
|
||||||
Get(name string) (*v1alpha1.Peer, error)
|
Get(name string) (*v1alpha1.Peer, error)
|
||||||
PeerListerExpansion
|
PeerListerExpansion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,12 @@
|
|||||||
package mesh
|
package mesh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"github.com/squat/kilo/pkg/wireguard"
|
"github.com/squat/kilo/pkg/wireguard"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,12 +50,15 @@ const (
|
|||||||
// FullGranularity indicates that the network should create
|
// FullGranularity indicates that the network should create
|
||||||
// a mesh between every node.
|
// a mesh between every node.
|
||||||
FullGranularity Granularity = "full"
|
FullGranularity Granularity = "full"
|
||||||
|
// AutoGranularity can be used with kgctl to obtain
|
||||||
|
// the granularity automatically.
|
||||||
|
AutoGranularity Granularity = "auto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Node represents a node in the network.
|
// Node represents a node in the network.
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Endpoint *wireguard.Endpoint
|
Endpoint *wireguard.Endpoint
|
||||||
Key []byte
|
Key wgtypes.Key
|
||||||
NoInternalIP bool
|
NoInternalIP bool
|
||||||
InternalIP *net.IPNet
|
InternalIP *net.IPNet
|
||||||
// LastSeen is a Unix time for the last time
|
// LastSeen is a Unix time for the last time
|
||||||
@@ -63,15 +69,23 @@ type Node struct {
|
|||||||
Leader bool
|
Leader bool
|
||||||
Location string
|
Location string
|
||||||
Name string
|
Name string
|
||||||
PersistentKeepalive int
|
PersistentKeepalive time.Duration
|
||||||
Subnet *net.IPNet
|
Subnet *net.IPNet
|
||||||
WireGuardIP *net.IPNet
|
WireGuardIP *net.IPNet
|
||||||
|
// DiscoveredEndpoints cannot be DNS endpoints, only net.UDPAddr.
|
||||||
|
DiscoveredEndpoints map[string]*net.UDPAddr
|
||||||
|
AllowedLocationIPs []net.IPNet
|
||||||
|
Granularity Granularity
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ready indicates whether or not the node is ready.
|
// Ready indicates whether or not the node is ready.
|
||||||
func (n *Node) Ready() bool {
|
func (n *Node) Ready() bool {
|
||||||
// Nodes that are not leaders will not have WireGuardIPs, so it is not required.
|
// Nodes that are not leaders will not have WireGuardIPs, so it is not required.
|
||||||
return n != nil && n.Endpoint != nil && !(n.Endpoint.IP == nil && n.Endpoint.DNS == "") && n.Endpoint.Port != 0 && n.Key != nil && n.Subnet != nil && time.Now().Unix()-n.LastSeen < int64(checkInPeriod)*2/int64(time.Second)
|
return n != nil &&
|
||||||
|
n.Endpoint.Ready() &&
|
||||||
|
n.Key != wgtypes.Key{} &&
|
||||||
|
n.Subnet != nil &&
|
||||||
|
time.Now().Unix()-n.LastSeen < int64(checkInPeriod)*2/int64(time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer represents a peer in the network.
|
// Peer represents a peer in the network.
|
||||||
@@ -86,7 +100,10 @@ type Peer struct {
|
|||||||
// will not declare their endpoint and instead allow it to be
|
// will not declare their endpoint and instead allow it to be
|
||||||
// discovered.
|
// discovered.
|
||||||
func (p *Peer) Ready() bool {
|
func (p *Peer) Ready() bool {
|
||||||
return p != nil && p.AllowedIPs != nil && len(p.AllowedIPs) != 0 && p.PublicKey != nil
|
return p != nil &&
|
||||||
|
p.AllowedIPs != nil &&
|
||||||
|
len(p.AllowedIPs) != 0 &&
|
||||||
|
p.PublicKey != wgtypes.Key{} // If Key was not set, it will be wgtypes.Key{}.
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventType describes what kind of an action an event represents.
|
// EventType describes what kind of an action an event represents.
|
||||||
@@ -130,11 +147,11 @@ type Backend interface {
|
|||||||
// clean up any changes applied to the backend,
|
// clean up any changes applied to the backend,
|
||||||
// and watch for changes to nodes.
|
// and watch for changes to nodes.
|
||||||
type NodeBackend interface {
|
type NodeBackend interface {
|
||||||
CleanUp(string) error
|
CleanUp(context.Context, string) error
|
||||||
Get(string) (*Node, error)
|
Get(string) (*Node, error)
|
||||||
Init(<-chan struct{}) error
|
Init(context.Context) error
|
||||||
List() ([]*Node, error)
|
List() ([]*Node, error)
|
||||||
Set(string, *Node) error
|
Set(context.Context, string, *Node) error
|
||||||
Watch() <-chan *NodeEvent
|
Watch() <-chan *NodeEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,10 +161,10 @@ type NodeBackend interface {
|
|||||||
// clean up any changes applied to the backend,
|
// clean up any changes applied to the backend,
|
||||||
// and watch for changes to peers.
|
// and watch for changes to peers.
|
||||||
type PeerBackend interface {
|
type PeerBackend interface {
|
||||||
CleanUp(string) error
|
CleanUp(context.Context, string) error
|
||||||
Get(string) (*Peer, error)
|
Get(string) (*Peer, error)
|
||||||
Init(<-chan struct{}) error
|
Init(context.Context) error
|
||||||
List() ([]*Peer, error)
|
List() ([]*Peer, error)
|
||||||
Set(string, *Peer) error
|
Set(context.Context, string, *Peer) error
|
||||||
Watch() <-chan *PeerEvent
|
Watch() <-chan *PeerEvent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build linux
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package mesh
|
package mesh
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build linux
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package mesh
|
package mesh
|
||||||
@@ -59,6 +60,7 @@ func getIP(hostname string, ignoreIfaces ...int) (*net.IPNet, *net.IPNet, error)
|
|||||||
ignore[oneAddressCIDR(ip.IP).String()] = struct{}{}
|
ignore[oneAddressCIDR(ip.IP).String()] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hostPriv, hostPub []*net.IPNet
|
var hostPriv, hostPub []*net.IPNet
|
||||||
{
|
{
|
||||||
// Check IPs to which hostname resolves first.
|
// Check IPs to which hostname resolves first.
|
||||||
@@ -71,6 +73,9 @@ func getIP(hostname string, ignoreIfaces ...int) (*net.IPNet, *net.IPNet, error)
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if isLocal(ip.IP) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
ip.Mask = mask
|
ip.Mask = mask
|
||||||
if isPublic(ip.IP) {
|
if isPublic(ip.IP) {
|
||||||
hostPub = append(hostPub, ip)
|
hostPub = append(hostPub, ip)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2019 the Kilo authors
|
// Copyright 2021 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/awalterschulze/gographviz"
|
"github.com/awalterschulze/gographviz"
|
||||||
|
|
||||||
"github.com/squat/kilo/pkg/wireguard"
|
"github.com/squat/kilo/pkg/wireguard"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -166,8 +167,9 @@ func nodeLabel(location, name string, cidr *net.IPNet, priv, wgIP net.IP, endpoi
|
|||||||
if wgIP != nil {
|
if wgIP != nil {
|
||||||
label = append(label, wgIP.String())
|
label = append(label, wgIP.String())
|
||||||
}
|
}
|
||||||
if endpoint != nil {
|
str := endpoint.String()
|
||||||
label = append(label, endpoint.String())
|
if str != "" {
|
||||||
|
label = append(label, str)
|
||||||
}
|
}
|
||||||
return graphEscape(strings.Join(label, "\\n"))
|
return graphEscape(strings.Join(label, "\\n"))
|
||||||
}
|
}
|
||||||
|
|||||||
467
pkg/mesh/mesh.go
467
pkg/mesh/mesh.go
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2019 the Kilo authors
|
// Copyright 2021 the Kilo authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,12 +12,14 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build linux
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package mesh
|
package mesh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
@@ -29,6 +31,8 @@ import (
|
|||||||
"github.com/go-kit/kit/log/level"
|
"github.com/go-kit/kit/log/level"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"github.com/squat/kilo/pkg/encapsulation"
|
"github.com/squat/kilo/pkg/encapsulation"
|
||||||
"github.com/squat/kilo/pkg/iproute"
|
"github.com/squat/kilo/pkg/iproute"
|
||||||
@@ -42,34 +46,32 @@ const (
|
|||||||
kiloPath = "/var/lib/kilo"
|
kiloPath = "/var/lib/kilo"
|
||||||
// privateKeyPath is the filepath where the WireGuard private key is stored.
|
// privateKeyPath is the filepath where the WireGuard private key is stored.
|
||||||
privateKeyPath = kiloPath + "/key"
|
privateKeyPath = kiloPath + "/key"
|
||||||
// confPath is the filepath where the WireGuard configuration is stored.
|
|
||||||
confPath = kiloPath + "/conf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mesh is able to create Kilo network meshes.
|
// Mesh is able to create Kilo network meshes.
|
||||||
type Mesh struct {
|
type Mesh struct {
|
||||||
Backend
|
Backend
|
||||||
cleanUpIface bool
|
cleanUpIface bool
|
||||||
cni bool
|
cni bool
|
||||||
cniPath string
|
cniPath string
|
||||||
enc encapsulation.Encapsulator
|
enc encapsulation.Encapsulator
|
||||||
externalIP *net.IPNet
|
externalIP *net.IPNet
|
||||||
granularity Granularity
|
granularity Granularity
|
||||||
hostname string
|
hostname string
|
||||||
internalIP *net.IPNet
|
internalIP *net.IPNet
|
||||||
ipTables *iptables.Controller
|
ipTables *iptables.Controller
|
||||||
kiloIface int
|
kiloIface int
|
||||||
key []byte
|
kiloIfaceName string
|
||||||
local bool
|
local bool
|
||||||
port uint32
|
port int
|
||||||
priv []byte
|
priv wgtypes.Key
|
||||||
privIface int
|
privIface int
|
||||||
pub []byte
|
pub wgtypes.Key
|
||||||
resyncPeriod time.Duration
|
resyncPeriod time.Duration
|
||||||
stop chan struct{}
|
iptablesForwardRule bool
|
||||||
subnet *net.IPNet
|
subnet *net.IPNet
|
||||||
table *route.Table
|
table *route.Table
|
||||||
wireGuardIP *net.IPNet
|
wireGuardIP *net.IPNet
|
||||||
|
|
||||||
// nodes and peers are mutable fields in the struct
|
// nodes and peers are mutable fields in the struct
|
||||||
// and need to be guarded.
|
// and need to be guarded.
|
||||||
@@ -86,23 +88,27 @@ type Mesh struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Mesh instance.
|
// New returns a new Mesh instance.
|
||||||
func New(backend Backend, enc encapsulation.Encapsulator, granularity Granularity, hostname string, port uint32, subnet *net.IPNet, local, cni bool, cniPath, iface string, cleanUpIface bool, createIface bool, resyncPeriod time.Duration, logger log.Logger) (*Mesh, error) {
|
func New(backend Backend, enc encapsulation.Encapsulator, granularity Granularity, hostname string, port int, subnet *net.IPNet, local, cni bool, cniPath, iface string, cleanUpIface bool, createIface bool, mtu uint, resyncPeriod time.Duration, prioritisePrivateAddr, iptablesForwardRule bool, logger log.Logger) (*Mesh, error) {
|
||||||
if err := os.MkdirAll(kiloPath, 0700); err != nil {
|
if err := os.MkdirAll(kiloPath, 0700); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create directory to store configuration: %v", err)
|
return nil, fmt.Errorf("failed to create directory to store configuration: %v", err)
|
||||||
}
|
}
|
||||||
private, err := ioutil.ReadFile(privateKeyPath)
|
privateB, err := ioutil.ReadFile(privateKeyPath)
|
||||||
private = bytes.Trim(private, "\n")
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("failed to read private key file: %v", err)
|
||||||
|
}
|
||||||
|
privateB = bytes.Trim(privateB, "\n")
|
||||||
|
private, err := wgtypes.ParseKey(string(privateB))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
level.Warn(logger).Log("msg", "no private key found on disk; generating one now")
|
level.Warn(logger).Log("msg", "no private key found on disk; generating one now")
|
||||||
if private, err = wireguard.GenKey(); err != nil {
|
if private, err = wgtypes.GeneratePrivateKey(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public, err := wireguard.PubKey(private)
|
public := private.PublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(privateKeyPath, private, 0600); err != nil {
|
if err := ioutil.WriteFile(privateKeyPath, []byte(private.String()), 0600); err != nil {
|
||||||
return nil, fmt.Errorf("failed to write private key to disk: %v", err)
|
return nil, fmt.Errorf("failed to write private key to disk: %v", err)
|
||||||
}
|
}
|
||||||
cniIndex, err := cniDeviceIndex()
|
cniIndex, err := cniDeviceIndex()
|
||||||
@@ -111,7 +117,7 @@ func New(backend Backend, enc encapsulation.Encapsulator, granularity Granularit
|
|||||||
}
|
}
|
||||||
var kiloIface int
|
var kiloIface int
|
||||||
if createIface {
|
if createIface {
|
||||||
kiloIface, _, err = wireguard.New(iface)
|
kiloIface, _, err = wireguard.New(iface, mtu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create WireGuard interface: %v", err)
|
return nil, fmt.Errorf("failed to create WireGuard interface: %v", err)
|
||||||
}
|
}
|
||||||
@@ -143,34 +149,41 @@ func New(backend Backend, enc encapsulation.Encapsulator, granularity Granularit
|
|||||||
enc = encapsulation.Noop(enc.Strategy())
|
enc = encapsulation.Noop(enc.Strategy())
|
||||||
level.Debug(logger).Log("msg", "running without a private IP address")
|
level.Debug(logger).Log("msg", "running without a private IP address")
|
||||||
}
|
}
|
||||||
|
var externalIP *net.IPNet
|
||||||
|
if prioritisePrivateAddr && privateIP != nil {
|
||||||
|
externalIP = privateIP
|
||||||
|
} else {
|
||||||
|
externalIP = publicIP
|
||||||
|
}
|
||||||
level.Debug(logger).Log("msg", fmt.Sprintf("using %s as the public IP address", publicIP.String()))
|
level.Debug(logger).Log("msg", fmt.Sprintf("using %s as the public IP address", publicIP.String()))
|
||||||
ipTables, err := iptables.New(iptables.WithLogger(log.With(logger, "component", "iptables")), iptables.WithResyncPeriod(resyncPeriod))
|
ipTables, err := iptables.New(iptables.WithLogger(log.With(logger, "component", "iptables")), iptables.WithResyncPeriod(resyncPeriod))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to IP tables controller: %v", err)
|
return nil, fmt.Errorf("failed to IP tables controller: %v", err)
|
||||||
}
|
}
|
||||||
return &Mesh{
|
return &Mesh{
|
||||||
Backend: backend,
|
Backend: backend,
|
||||||
cleanUpIface: cleanUpIface,
|
cleanUpIface: cleanUpIface,
|
||||||
cni: cni,
|
cni: cni,
|
||||||
cniPath: cniPath,
|
cniPath: cniPath,
|
||||||
enc: enc,
|
enc: enc,
|
||||||
externalIP: publicIP,
|
externalIP: externalIP,
|
||||||
granularity: granularity,
|
granularity: granularity,
|
||||||
hostname: hostname,
|
hostname: hostname,
|
||||||
internalIP: privateIP,
|
internalIP: privateIP,
|
||||||
ipTables: ipTables,
|
ipTables: ipTables,
|
||||||
kiloIface: kiloIface,
|
kiloIface: kiloIface,
|
||||||
nodes: make(map[string]*Node),
|
kiloIfaceName: iface,
|
||||||
peers: make(map[string]*Peer),
|
nodes: make(map[string]*Node),
|
||||||
port: port,
|
peers: make(map[string]*Peer),
|
||||||
priv: private,
|
port: port,
|
||||||
privIface: privIface,
|
priv: private,
|
||||||
pub: public,
|
privIface: privIface,
|
||||||
resyncPeriod: resyncPeriod,
|
pub: public,
|
||||||
local: local,
|
resyncPeriod: resyncPeriod,
|
||||||
stop: make(chan struct{}),
|
iptablesForwardRule: iptablesForwardRule,
|
||||||
subnet: subnet,
|
local: local,
|
||||||
table: route.NewTable(),
|
subnet: subnet,
|
||||||
|
table: route.NewTable(),
|
||||||
errorCounter: prometheus.NewCounterVec(prometheus.CounterOpts{
|
errorCounter: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
Name: "kilo_errors_total",
|
Name: "kilo_errors_total",
|
||||||
Help: "Number of errors that occurred while administering the mesh.",
|
Help: "Number of errors that occurred while administering the mesh.",
|
||||||
@@ -196,8 +209,8 @@ func New(backend Backend, enc encapsulation.Encapsulator, granularity Granularit
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run starts the mesh.
|
// Run starts the mesh.
|
||||||
func (m *Mesh) Run() error {
|
func (m *Mesh) Run(ctx context.Context) error {
|
||||||
if err := m.Nodes().Init(m.stop); err != nil {
|
if err := m.Nodes().Init(ctx); err != nil {
|
||||||
return fmt.Errorf("failed to initialize node backend: %v", err)
|
return fmt.Errorf("failed to initialize node backend: %v", err)
|
||||||
}
|
}
|
||||||
// Try to set the CNI config quickly.
|
// Try to set the CNI config quickly.
|
||||||
@@ -209,14 +222,14 @@ func (m *Mesh) Run() error {
|
|||||||
level.Warn(m.logger).Log("error", fmt.Errorf("failed to get node %q: %v", m.hostname, err))
|
level.Warn(m.logger).Log("error", fmt.Errorf("failed to get node %q: %v", m.hostname, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := m.Peers().Init(m.stop); err != nil {
|
if err := m.Peers().Init(ctx); err != nil {
|
||||||
return fmt.Errorf("failed to initialize peer backend: %v", err)
|
return fmt.Errorf("failed to initialize peer backend: %v", err)
|
||||||
}
|
}
|
||||||
ipTablesErrors, err := m.ipTables.Run(m.stop)
|
ipTablesErrors, err := m.ipTables.Run(ctx.Done())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to watch for IP tables updates: %v", err)
|
return fmt.Errorf("failed to watch for IP tables updates: %v", err)
|
||||||
}
|
}
|
||||||
routeErrors, err := m.table.Run(m.stop)
|
routeErrors, err := m.table.Run(ctx.Done())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to watch for route table updates: %v", err)
|
return fmt.Errorf("failed to watch for route table updates: %v", err)
|
||||||
}
|
}
|
||||||
@@ -226,7 +239,7 @@ func (m *Mesh) Run() error {
|
|||||||
select {
|
select {
|
||||||
case err = <-ipTablesErrors:
|
case err = <-ipTablesErrors:
|
||||||
case err = <-routeErrors:
|
case err = <-routeErrors:
|
||||||
case <-m.stop:
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -245,11 +258,11 @@ func (m *Mesh) Run() error {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case ne = <-nw:
|
case ne = <-nw:
|
||||||
m.syncNodes(ne)
|
m.syncNodes(ctx, ne)
|
||||||
case pe = <-pw:
|
case pe = <-pw:
|
||||||
m.syncPeers(pe)
|
m.syncPeers(pe)
|
||||||
case <-checkIn.C:
|
case <-checkIn.C:
|
||||||
m.checkIn()
|
m.checkIn(ctx)
|
||||||
checkIn.Reset(checkInPeriod)
|
checkIn.Reset(checkInPeriod)
|
||||||
case <-resync.C:
|
case <-resync.C:
|
||||||
if m.cni {
|
if m.cni {
|
||||||
@@ -257,45 +270,40 @@ func (m *Mesh) Run() error {
|
|||||||
}
|
}
|
||||||
m.applyTopology()
|
m.applyTopology()
|
||||||
resync.Reset(m.resyncPeriod)
|
resync.Reset(m.resyncPeriod)
|
||||||
case <-m.stop:
|
case <-ctx.Done():
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mesh) syncNodes(e *NodeEvent) {
|
func (m *Mesh) syncNodes(ctx context.Context, e *NodeEvent) {
|
||||||
logger := log.With(m.logger, "event", e.Type)
|
logger := log.With(m.logger, "event", e.Type)
|
||||||
level.Debug(logger).Log("msg", "syncing nodes", "event", e.Type)
|
level.Debug(logger).Log("msg", "syncing nodes", "event", e.Type)
|
||||||
if isSelf(m.hostname, e.Node) {
|
if isSelf(m.hostname, e.Node) {
|
||||||
level.Debug(logger).Log("msg", "processing local node", "node", e.Node)
|
level.Debug(logger).Log("msg", "processing local node", "node", e.Node)
|
||||||
m.handleLocal(e.Node)
|
m.handleLocal(ctx, e.Node)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var diff bool
|
var diff bool
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
if !e.Node.Ready() {
|
if !e.Node.Ready() {
|
||||||
level.Debug(logger).Log("msg", "received incomplete node", "node", e.Node)
|
// Trace non ready nodes with their presence in the mesh.
|
||||||
// An existing node is no longer valid
|
_, ok := m.nodes[e.Node.Name]
|
||||||
// so remove it from the mesh.
|
level.Debug(logger).Log("msg", "received non ready node", "node", e.Node, "in-mesh", ok)
|
||||||
if _, ok := m.nodes[e.Node.Name]; ok {
|
}
|
||||||
level.Info(logger).Log("msg", "node is no longer ready", "node", e.Node)
|
switch e.Type {
|
||||||
diff = true
|
case AddEvent:
|
||||||
}
|
fallthrough
|
||||||
} else {
|
case UpdateEvent:
|
||||||
switch e.Type {
|
if !nodesAreEqual(m.nodes[e.Node.Name], e.Node) {
|
||||||
case AddEvent:
|
|
||||||
fallthrough
|
|
||||||
case UpdateEvent:
|
|
||||||
if !nodesAreEqual(m.nodes[e.Node.Name], e.Node) {
|
|
||||||
diff = true
|
|
||||||
}
|
|
||||||
// Even if the nodes are the same,
|
|
||||||
// overwrite the old node to update the timestamp.
|
|
||||||
m.nodes[e.Node.Name] = e.Node
|
|
||||||
case DeleteEvent:
|
|
||||||
delete(m.nodes, e.Node.Name)
|
|
||||||
diff = true
|
diff = true
|
||||||
}
|
}
|
||||||
|
// Even if the nodes are the same,
|
||||||
|
// overwrite the old node to update the timestamp.
|
||||||
|
m.nodes[e.Node.Name] = e.Node
|
||||||
|
case DeleteEvent:
|
||||||
|
delete(m.nodes, e.Node.Name)
|
||||||
|
diff = true
|
||||||
}
|
}
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
if diff {
|
if diff {
|
||||||
@@ -310,32 +318,27 @@ func (m *Mesh) syncPeers(e *PeerEvent) {
|
|||||||
var diff bool
|
var diff bool
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
// Peers are indexed by public key.
|
// Peers are indexed by public key.
|
||||||
key := string(e.Peer.PublicKey)
|
key := e.Peer.PublicKey.String()
|
||||||
if !e.Peer.Ready() {
|
if !e.Peer.Ready() {
|
||||||
level.Debug(logger).Log("msg", "received incomplete peer", "peer", e.Peer)
|
// Trace non ready peer with their presence in the mesh.
|
||||||
// An existing peer is no longer valid
|
_, ok := m.peers[key]
|
||||||
// so remove it from the mesh.
|
level.Debug(logger).Log("msg", "received non ready peer", "peer", e.Peer, "in-mesh", ok)
|
||||||
if _, ok := m.peers[key]; ok {
|
}
|
||||||
level.Info(logger).Log("msg", "peer is no longer ready", "peer", e.Peer)
|
switch e.Type {
|
||||||
|
case AddEvent:
|
||||||
|
fallthrough
|
||||||
|
case UpdateEvent:
|
||||||
|
if e.Old != nil && key != e.Old.PublicKey.String() {
|
||||||
|
delete(m.peers, e.Old.PublicKey.String())
|
||||||
diff = true
|
diff = true
|
||||||
}
|
}
|
||||||
} else {
|
if !peersAreEqual(m.peers[key], e.Peer) {
|
||||||
switch e.Type {
|
m.peers[key] = e.Peer
|
||||||
case AddEvent:
|
|
||||||
fallthrough
|
|
||||||
case UpdateEvent:
|
|
||||||
if e.Old != nil && key != string(e.Old.PublicKey) {
|
|
||||||
delete(m.peers, string(e.Old.PublicKey))
|
|
||||||
diff = true
|
|
||||||
}
|
|
||||||
if !peersAreEqual(m.peers[key], e.Peer) {
|
|
||||||
m.peers[key] = e.Peer
|
|
||||||
diff = true
|
|
||||||
}
|
|
||||||
case DeleteEvent:
|
|
||||||
delete(m.peers, key)
|
|
||||||
diff = true
|
diff = true
|
||||||
}
|
}
|
||||||
|
case DeleteEvent:
|
||||||
|
delete(m.peers, key)
|
||||||
|
diff = true
|
||||||
}
|
}
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
if diff {
|
if diff {
|
||||||
@@ -346,7 +349,7 @@ func (m *Mesh) syncPeers(e *PeerEvent) {
|
|||||||
|
|
||||||
// checkIn will try to update the local node's LastSeen timestamp
|
// checkIn will try to update the local node's LastSeen timestamp
|
||||||
// in the backend.
|
// in the backend.
|
||||||
func (m *Mesh) checkIn() {
|
func (m *Mesh) checkIn(ctx context.Context) {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
n := m.nodes[m.hostname]
|
n := m.nodes[m.hostname]
|
||||||
@@ -356,7 +359,7 @@ func (m *Mesh) checkIn() {
|
|||||||
}
|
}
|
||||||
oldTime := n.LastSeen
|
oldTime := n.LastSeen
|
||||||
n.LastSeen = time.Now().Unix()
|
n.LastSeen = time.Now().Unix()
|
||||||
if err := m.Nodes().Set(m.hostname, n); err != nil {
|
if err := m.Nodes().Set(ctx, m.hostname, n); err != nil {
|
||||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to set local node: %v", err), "node", n)
|
level.Error(m.logger).Log("error", fmt.Sprintf("failed to set local node: %v", err), "node", n)
|
||||||
m.errorCounter.WithLabelValues("checkin").Inc()
|
m.errorCounter.WithLabelValues("checkin").Inc()
|
||||||
// Revert time.
|
// Revert time.
|
||||||
@@ -366,10 +369,12 @@ func (m *Mesh) checkIn() {
|
|||||||
level.Debug(m.logger).Log("msg", "successfully checked in local node in backend")
|
level.Debug(m.logger).Log("msg", "successfully checked in local node in backend")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mesh) handleLocal(n *Node) {
|
func (m *Mesh) handleLocal(ctx context.Context, n *Node) {
|
||||||
// Allow the IPs to be overridden.
|
// Allow the IPs to be overridden.
|
||||||
if n.Endpoint == nil || (n.Endpoint.DNS == "" && n.Endpoint.IP == nil) {
|
if !n.Endpoint.Ready() {
|
||||||
n.Endpoint = &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: m.externalIP.IP}, Port: m.port}
|
e := wireguard.NewEndpoint(m.externalIP.IP, m.port)
|
||||||
|
level.Info(m.logger).Log("msg", "overriding endpoint", "node", m.hostname, "old endpoint", n.Endpoint.String(), "new endpoint", e.String())
|
||||||
|
n.Endpoint = e
|
||||||
}
|
}
|
||||||
if n.InternalIP == nil && !n.NoInternalIP {
|
if n.InternalIP == nil && !n.NoInternalIP {
|
||||||
n.InternalIP = m.internalIP
|
n.InternalIP = m.internalIP
|
||||||
@@ -389,10 +394,13 @@ func (m *Mesh) handleLocal(n *Node) {
|
|||||||
PersistentKeepalive: n.PersistentKeepalive,
|
PersistentKeepalive: n.PersistentKeepalive,
|
||||||
Subnet: n.Subnet,
|
Subnet: n.Subnet,
|
||||||
WireGuardIP: m.wireGuardIP,
|
WireGuardIP: m.wireGuardIP,
|
||||||
|
DiscoveredEndpoints: n.DiscoveredEndpoints,
|
||||||
|
AllowedLocationIPs: n.AllowedLocationIPs,
|
||||||
|
Granularity: m.granularity,
|
||||||
}
|
}
|
||||||
if !nodesAreEqual(n, local) {
|
if !nodesAreEqual(n, local) {
|
||||||
level.Debug(m.logger).Log("msg", "local node differs from backend")
|
level.Debug(m.logger).Log("msg", "local node differs from backend")
|
||||||
if err := m.Nodes().Set(m.hostname, local); err != nil {
|
if err := m.Nodes().Set(ctx, m.hostname, local); err != nil {
|
||||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to set local node: %v", err), "node", local)
|
level.Error(m.logger).Log("error", fmt.Sprintf("failed to set local node: %v", err), "node", local)
|
||||||
m.errorCounter.WithLabelValues("local").Inc()
|
m.errorCounter.WithLabelValues("local").Inc()
|
||||||
return
|
return
|
||||||
@@ -428,12 +436,12 @@ func (m *Mesh) applyTopology() {
|
|||||||
nodes := make(map[string]*Node)
|
nodes := make(map[string]*Node)
|
||||||
var readyNodes float64
|
var readyNodes float64
|
||||||
for k := range m.nodes {
|
for k := range m.nodes {
|
||||||
|
m.nodes[k].Granularity = m.granularity
|
||||||
if !m.nodes[k].Ready() {
|
if !m.nodes[k].Ready() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Make a shallow copy of the node.
|
// Make it point to the node without copy.
|
||||||
node := *m.nodes[k]
|
nodes[k] = m.nodes[k]
|
||||||
nodes[k] = &node
|
|
||||||
readyNodes++
|
readyNodes++
|
||||||
}
|
}
|
||||||
// Ensure only ready nodes are considered.
|
// Ensure only ready nodes are considered.
|
||||||
@@ -443,9 +451,8 @@ func (m *Mesh) applyTopology() {
|
|||||||
if !m.peers[k].Ready() {
|
if !m.peers[k].Ready() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Make a shallow copy of the peer.
|
// Make it point the peer without copy.
|
||||||
peer := *m.peers[k]
|
peers[k] = m.peers[k]
|
||||||
peers[k] = &peer
|
|
||||||
readyPeers++
|
readyPeers++
|
||||||
}
|
}
|
||||||
m.nodesGuage.Set(readyNodes)
|
m.nodesGuage.Set(readyNodes)
|
||||||
@@ -461,16 +468,26 @@ func (m *Mesh) applyTopology() {
|
|||||||
m.errorCounter.WithLabelValues("apply").Inc()
|
m.errorCounter.WithLabelValues("apply").Inc()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Find the old configuration.
|
|
||||||
oldConfRaw, err := wireguard.ShowConf(link.Attrs().Name)
|
wgClient, err := wgctrl.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
level.Error(m.logger).Log("error", err)
|
level.Error(m.logger).Log("error", err)
|
||||||
m.errorCounter.WithLabelValues("apply").Inc()
|
m.errorCounter.WithLabelValues("apply").Inc()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
oldConf := wireguard.Parse(oldConfRaw)
|
defer wgClient.Close()
|
||||||
updateNATEndpoints(nodes, peers, oldConf)
|
|
||||||
t, err := NewTopology(nodes, peers, m.granularity, m.hostname, nodes[m.hostname].Endpoint.Port, m.priv, m.subnet, nodes[m.hostname].PersistentKeepalive)
|
// wgDevice is the current configuration of the wg interface.
|
||||||
|
wgDevice, err := wgClient.Device(m.kiloIfaceName)
|
||||||
|
if err != nil {
|
||||||
|
level.Error(m.logger).Log("error", err)
|
||||||
|
m.errorCounter.WithLabelValues("apply").Inc()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
natEndpoints := discoverNATEndpoints(nodes, peers, wgDevice, m.logger)
|
||||||
|
nodes[m.hostname].DiscoveredEndpoints = natEndpoints
|
||||||
|
t, err := NewTopology(nodes, peers, m.granularity, m.hostname, nodes[m.hostname].Endpoint.Port(), m.priv, m.subnet, nodes[m.hostname].PersistentKeepalive, m.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
level.Error(m.logger).Log("error", err)
|
level.Error(m.logger).Log("error", err)
|
||||||
m.errorCounter.WithLabelValues("apply").Inc()
|
m.errorCounter.WithLabelValues("apply").Inc()
|
||||||
@@ -482,19 +499,8 @@ func (m *Mesh) applyTopology() {
|
|||||||
} else {
|
} else {
|
||||||
m.wireGuardIP = nil
|
m.wireGuardIP = nil
|
||||||
}
|
}
|
||||||
conf := t.Conf()
|
ipRules := t.Rules(m.cni, m.iptablesForwardRule)
|
||||||
buf, err := conf.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
level.Error(m.logger).Log("error", err)
|
|
||||||
m.errorCounter.WithLabelValues("apply").Inc()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(confPath, buf, 0600); err != nil {
|
|
||||||
level.Error(m.logger).Log("error", err)
|
|
||||||
m.errorCounter.WithLabelValues("apply").Inc()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ipRules := t.Rules(m.cni)
|
|
||||||
// If we are handling local routes, ensure the local
|
// If we are handling local routes, ensure the local
|
||||||
// tunnel has an IP address and IPIP traffic is allowed.
|
// tunnel has an IP address and IPIP traffic is allowed.
|
||||||
if m.enc.Strategy() != encapsulation.Never && m.local {
|
if m.enc.Strategy() != encapsulation.Never && m.local {
|
||||||
@@ -533,10 +539,12 @@ func (m *Mesh) applyTopology() {
|
|||||||
}
|
}
|
||||||
// Setting the WireGuard configuration interrupts existing connections
|
// Setting the WireGuard configuration interrupts existing connections
|
||||||
// so only set the configuration if it has changed.
|
// so only set the configuration if it has changed.
|
||||||
equal := conf.Equal(oldConf)
|
conf := t.Conf()
|
||||||
|
equal, diff := conf.Equal(wgDevice)
|
||||||
if !equal {
|
if !equal {
|
||||||
level.Info(m.logger).Log("msg", "WireGuard configurations are different")
|
level.Info(m.logger).Log("msg", "WireGuard configurations are different", "diff", diff)
|
||||||
if err := wireguard.SetConf(link.Attrs().Name, confPath); err != nil {
|
level.Debug(m.logger).Log("msg", "changing wg config", "config", conf.WGConfig())
|
||||||
|
if err := wgClient.ConfigureDevice(m.kiloIfaceName, conf.WGConfig()); err != nil {
|
||||||
level.Error(m.logger).Log("error", err)
|
level.Error(m.logger).Log("error", err)
|
||||||
m.errorCounter.WithLabelValues("apply").Inc()
|
m.errorCounter.WithLabelValues("apply").Inc()
|
||||||
return
|
return
|
||||||
@@ -577,11 +585,6 @@ func (m *Mesh) RegisterMetrics(r prometheus.Registerer) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the mesh.
|
|
||||||
func (m *Mesh) Stop() {
|
|
||||||
close(m.stop)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mesh) cleanUp() {
|
func (m *Mesh) cleanUp() {
|
||||||
if err := m.ipTables.CleanUp(); err != nil {
|
if err := m.ipTables.CleanUp(); err != nil {
|
||||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up IP tables: %v", err))
|
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up IP tables: %v", err))
|
||||||
@@ -591,23 +594,27 @@ func (m *Mesh) cleanUp() {
|
|||||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up routes: %v", err))
|
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up routes: %v", err))
|
||||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||||
}
|
}
|
||||||
if err := os.Remove(confPath); err != nil {
|
|
||||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to delete configuration file: %v", err))
|
|
||||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
|
||||||
}
|
|
||||||
if m.cleanUpIface {
|
if m.cleanUpIface {
|
||||||
if err := iproute.RemoveInterface(m.kiloIface); err != nil {
|
if err := iproute.RemoveInterface(m.kiloIface); err != nil {
|
||||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to remove WireGuard interface: %v", err))
|
level.Error(m.logger).Log("error", fmt.Sprintf("failed to remove WireGuard interface: %v", err))
|
||||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := m.Nodes().CleanUp(m.hostname); err != nil {
|
{
|
||||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up node backend: %v", err))
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
defer cancel()
|
||||||
|
if err := m.Nodes().CleanUp(ctx, m.hostname); err != nil {
|
||||||
|
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up node backend: %v", err))
|
||||||
|
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := m.Peers().CleanUp(m.hostname); err != nil {
|
{
|
||||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up peer backend: %v", err))
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
defer cancel()
|
||||||
|
if err := m.Peers().CleanUp(ctx, m.hostname); err != nil {
|
||||||
|
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up peer backend: %v", err))
|
||||||
|
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := m.enc.CleanUp(); err != nil {
|
if err := m.enc.CleanUp(); err != nil {
|
||||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up encapsulator: %v", err))
|
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up encapsulator: %v", err))
|
||||||
@@ -622,12 +629,8 @@ func (m *Mesh) resolveEndpoints() error {
|
|||||||
if !m.nodes[k].Ready() {
|
if !m.nodes[k].Ready() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// If the node is ready, then the endpoint is not nil
|
// Resolve the Endpoint
|
||||||
// but it may not have a DNS name.
|
if _, err := m.nodes[k].Endpoint.UDPAddr(true); err != nil {
|
||||||
if m.nodes[k].Endpoint.DNS == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := resolveEndpoint(m.nodes[k].Endpoint); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -638,33 +641,16 @@ func (m *Mesh) resolveEndpoints() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Peers may have nil endpoints.
|
// Peers may have nil endpoints.
|
||||||
if m.peers[k].Endpoint == nil || m.peers[k].Endpoint.DNS == "" {
|
if !m.peers[k].Endpoint.Ready() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := resolveEndpoint(m.peers[k].Endpoint); err != nil {
|
if _, err := m.peers[k].Endpoint.UDPAddr(true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveEndpoint(endpoint *wireguard.Endpoint) error {
|
|
||||||
ips, err := net.LookupIP(endpoint.DNS)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to look up DNS name %q: %v", endpoint.DNS, err)
|
|
||||||
}
|
|
||||||
nets := make([]*net.IPNet, len(ips), len(ips))
|
|
||||||
for i := range ips {
|
|
||||||
nets[i] = oneAddressCIDR(ips[i])
|
|
||||||
}
|
|
||||||
sortIPs(nets)
|
|
||||||
if len(nets) == 0 {
|
|
||||||
return fmt.Errorf("did not find any addresses for DNS name %q", endpoint.DNS)
|
|
||||||
}
|
|
||||||
endpoint.IP = nets[0].IP
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSelf(hostname string, node *Node) bool {
|
func isSelf(hostname string, node *Node) bool {
|
||||||
return node != nil && node.Name == hostname
|
return node != nil && node.Name == hostname
|
||||||
}
|
}
|
||||||
@@ -676,26 +662,26 @@ func nodesAreEqual(a, b *Node) bool {
|
|||||||
if a == b {
|
if a == b {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !(a.Endpoint != nil) == (b.Endpoint != nil) {
|
// Check the DNS name first since this package
|
||||||
|
// is doing the DNS resolution.
|
||||||
|
if !a.Endpoint.Equal(b.Endpoint, true) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if a.Endpoint != nil {
|
|
||||||
if a.Endpoint.Port != b.Endpoint.Port {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Check the DNS name first since this package
|
|
||||||
// is doing the DNS resolution.
|
|
||||||
if a.Endpoint.DNS != b.Endpoint.DNS {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if a.Endpoint.DNS == "" && !a.Endpoint.IP.Equal(b.Endpoint.IP) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Ignore LastSeen when comparing equality we want to check if the nodes are
|
// Ignore LastSeen when comparing equality we want to check if the nodes are
|
||||||
// equivalent. However, we do want to check if LastSeen has transitioned
|
// equivalent. However, we do want to check if LastSeen has transitioned
|
||||||
// between valid and invalid.
|
// between valid and invalid.
|
||||||
return string(a.Key) == string(b.Key) && ipNetsEqual(a.WireGuardIP, b.WireGuardIP) && ipNetsEqual(a.InternalIP, b.InternalIP) && a.Leader == b.Leader && a.Location == b.Location && a.Name == b.Name && subnetsEqual(a.Subnet, b.Subnet) && a.Ready() == b.Ready() && a.PersistentKeepalive == b.PersistentKeepalive
|
return a.Key.String() == b.Key.String() &&
|
||||||
|
ipNetsEqual(a.WireGuardIP, b.WireGuardIP) &&
|
||||||
|
ipNetsEqual(a.InternalIP, b.InternalIP) &&
|
||||||
|
a.Leader == b.Leader &&
|
||||||
|
a.Location == b.Location &&
|
||||||
|
a.Name == b.Name &&
|
||||||
|
subnetsEqual(a.Subnet, b.Subnet) &&
|
||||||
|
a.Ready() == b.Ready() &&
|
||||||
|
a.PersistentKeepalive == b.PersistentKeepalive &&
|
||||||
|
discoveredEndpointsAreEqual(a.DiscoveredEndpoints, b.DiscoveredEndpoints) &&
|
||||||
|
ipNetSlicesEqual(a.AllowedLocationIPs, b.AllowedLocationIPs) &&
|
||||||
|
a.Granularity == b.Granularity
|
||||||
}
|
}
|
||||||
|
|
||||||
func peersAreEqual(a, b *Peer) bool {
|
func peersAreEqual(a, b *Peer) bool {
|
||||||
@@ -705,31 +691,24 @@ func peersAreEqual(a, b *Peer) bool {
|
|||||||
if a == b {
|
if a == b {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !(a.Endpoint != nil) == (b.Endpoint != nil) {
|
// Check the DNS name first since this package
|
||||||
|
// is doing the DNS resolution.
|
||||||
|
if !a.Endpoint.Equal(b.Endpoint, true) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if a.Endpoint != nil {
|
|
||||||
if a.Endpoint.Port != b.Endpoint.Port {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Check the DNS name first since this package
|
|
||||||
// is doing the DNS resolution.
|
|
||||||
if a.Endpoint.DNS != b.Endpoint.DNS {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if a.Endpoint.DNS == "" && !a.Endpoint.IP.Equal(b.Endpoint.IP) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(a.AllowedIPs) != len(b.AllowedIPs) {
|
if len(a.AllowedIPs) != len(b.AllowedIPs) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i := range a.AllowedIPs {
|
for i := range a.AllowedIPs {
|
||||||
if !ipNetsEqual(a.AllowedIPs[i], b.AllowedIPs[i]) {
|
if !ipNetsEqual(&a.AllowedIPs[i], &b.AllowedIPs[i]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return string(a.PublicKey) == string(b.PublicKey) && string(a.PresharedKey) == string(b.PresharedKey) && a.PersistentKeepalive == b.PersistentKeepalive
|
return a.PublicKey.String() == b.PublicKey.String() &&
|
||||||
|
(a.PresharedKey == nil) == (b.PresharedKey == nil) &&
|
||||||
|
(a.PresharedKey == nil || a.PresharedKey.String() == b.PresharedKey.String()) &&
|
||||||
|
(a.PersistentKeepaliveInterval == nil) == (b.PersistentKeepaliveInterval == nil) &&
|
||||||
|
(a.PersistentKeepaliveInterval == nil || *a.PersistentKeepaliveInterval == *b.PersistentKeepaliveInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ipNetsEqual(a, b *net.IPNet) bool {
|
func ipNetsEqual(a, b *net.IPNet) bool {
|
||||||
@@ -745,6 +724,18 @@ func ipNetsEqual(a, b *net.IPNet) bool {
|
|||||||
return a.IP.Equal(b.IP)
|
return a.IP.Equal(b.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ipNetSlicesEqual(a, b []net.IPNet) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range a {
|
||||||
|
if !ipNetsEqual(&a[i], &b[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func subnetsEqual(a, b *net.IPNet) bool {
|
func subnetsEqual(a, b *net.IPNet) bool {
|
||||||
if a == nil && b == nil {
|
if a == nil && b == nil {
|
||||||
return true
|
return true
|
||||||
@@ -764,6 +755,37 @@ func subnetsEqual(a, b *net.IPNet) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func udpAddrsEqual(a, b *net.UDPAddr) bool {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (a != nil) != (b != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if a.Zone != b.Zone {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if a.Port != b.Port {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return a.IP.Equal(b.IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func discoveredEndpointsAreEqual(a, b map[string]*net.UDPAddr) bool {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for k := range a {
|
||||||
|
if !udpAddrsEqual(a[k], b[k]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func linkByIndex(index int) (netlink.Link, error) {
|
func linkByIndex(index int) (netlink.Link, error) {
|
||||||
link, err := netlink.LinkByIndex(index)
|
link, err := netlink.LinkByIndex(index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -772,21 +794,30 @@ func linkByIndex(index int) (netlink.Link, error) {
|
|||||||
return link, nil
|
return link, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateNATEndpoints ensures that nodes and peers behind NAT update
|
// discoverNATEndpoints uses the node's WireGuard configuration to returns a list of the most recently discovered endpoints for all nodes and peers behind NAT so that they can roam.
|
||||||
// their endpoints from the WireGuard configuration so they can roam.
|
// Discovered endpionts will never be DNS names, because WireGuard will always resolve them to net.UDPAddr.
|
||||||
func updateNATEndpoints(nodes map[string]*Node, peers map[string]*Peer, conf *wireguard.Conf) {
|
func discoverNATEndpoints(nodes map[string]*Node, peers map[string]*Peer, conf *wgtypes.Device, logger log.Logger) map[string]*net.UDPAddr {
|
||||||
keys := make(map[string]*wireguard.Peer)
|
natEndpoints := make(map[string]*net.UDPAddr)
|
||||||
|
keys := make(map[string]wgtypes.Peer)
|
||||||
for i := range conf.Peers {
|
for i := range conf.Peers {
|
||||||
keys[string(conf.Peers[i].PublicKey)] = conf.Peers[i]
|
keys[conf.Peers[i].PublicKey.String()] = conf.Peers[i]
|
||||||
}
|
}
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
if peer, ok := keys[string(n.Key)]; ok && n.PersistentKeepalive > 0 {
|
if peer, ok := keys[n.Key.String()]; ok && n.PersistentKeepalive != time.Duration(0) {
|
||||||
n.Endpoint = peer.Endpoint
|
level.Debug(logger).Log("msg", "WireGuard Update NAT Endpoint", "node", n.Name, "endpoint", peer.Endpoint, "former-endpoint", n.Endpoint, "same", peer.Endpoint.String() == n.Endpoint.String(), "latest-handshake", peer.LastHandshakeTime)
|
||||||
|
// Don't update the endpoint, if there was never any handshake.
|
||||||
|
if !peer.LastHandshakeTime.Equal(time.Time{}) {
|
||||||
|
natEndpoints[n.Key.String()] = peer.Endpoint
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range peers {
|
for _, p := range peers {
|
||||||
if peer, ok := keys[string(p.PublicKey)]; ok && p.PersistentKeepalive > 0 {
|
if peer, ok := keys[p.PublicKey.String()]; ok && p.PersistentKeepaliveInterval != nil {
|
||||||
p.Endpoint = peer.Endpoint
|
if !peer.LastHandshakeTime.Equal(time.Time{}) {
|
||||||
|
natEndpoints[p.PublicKey.String()] = peer.Endpoint
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
level.Debug(logger).Log("msg", "Discovered WireGuard NAT Endpoints", "DiscoveredEndpoints", natEndpoints)
|
||||||
|
return natEndpoints
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,21 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"github.com/squat/kilo/pkg/wireguard"
|
"github.com/squat/kilo/pkg/wireguard"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func mustKey() wgtypes.Key {
|
||||||
|
if k, err := wgtypes.GeneratePrivateKey(); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
} else {
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = mustKey()
|
||||||
|
|
||||||
func TestReady(t *testing.T) {
|
func TestReady(t *testing.T) {
|
||||||
internalIP := oneAddressCIDR(net.ParseIP("1.1.1.1"))
|
internalIP := oneAddressCIDR(net.ParseIP("1.1.1.1"))
|
||||||
externalIP := oneAddressCIDR(net.ParseIP("2.2.2.2"))
|
externalIP := oneAddressCIDR(net.ParseIP("2.2.2.2"))
|
||||||
@@ -44,7 +56,7 @@ func TestReady(t *testing.T) {
|
|||||||
name: "empty endpoint",
|
name: "empty endpoint",
|
||||||
node: &Node{
|
node: &Node{
|
||||||
InternalIP: internalIP,
|
InternalIP: internalIP,
|
||||||
Key: []byte{},
|
Key: key,
|
||||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
||||||
},
|
},
|
||||||
ready: false,
|
ready: false,
|
||||||
@@ -52,9 +64,9 @@ func TestReady(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "empty endpoint IP",
|
name: "empty endpoint IP",
|
||||||
node: &Node{
|
node: &Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{}, Port: DefaultKiloPort},
|
Endpoint: wireguard.NewEndpoint(nil, DefaultKiloPort),
|
||||||
InternalIP: internalIP,
|
InternalIP: internalIP,
|
||||||
Key: []byte{},
|
Key: wgtypes.Key{},
|
||||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
||||||
},
|
},
|
||||||
ready: false,
|
ready: false,
|
||||||
@@ -62,9 +74,9 @@ func TestReady(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "empty endpoint port",
|
name: "empty endpoint port",
|
||||||
node: &Node{
|
node: &Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: externalIP.IP}},
|
Endpoint: wireguard.NewEndpoint(externalIP.IP, 0),
|
||||||
InternalIP: internalIP,
|
InternalIP: internalIP,
|
||||||
Key: []byte{},
|
Key: wgtypes.Key{},
|
||||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
||||||
},
|
},
|
||||||
ready: false,
|
ready: false,
|
||||||
@@ -72,8 +84,8 @@ func TestReady(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "empty internal IP",
|
name: "empty internal IP",
|
||||||
node: &Node{
|
node: &Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: externalIP.IP}, Port: DefaultKiloPort},
|
Endpoint: wireguard.NewEndpoint(externalIP.IP, DefaultKiloPort),
|
||||||
Key: []byte{},
|
Key: wgtypes.Key{},
|
||||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
||||||
},
|
},
|
||||||
ready: false,
|
ready: false,
|
||||||
@@ -81,7 +93,7 @@ func TestReady(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "empty key",
|
name: "empty key",
|
||||||
node: &Node{
|
node: &Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: externalIP.IP}, Port: DefaultKiloPort},
|
Endpoint: wireguard.NewEndpoint(externalIP.IP, DefaultKiloPort),
|
||||||
InternalIP: internalIP,
|
InternalIP: internalIP,
|
||||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
||||||
},
|
},
|
||||||
@@ -90,18 +102,18 @@ func TestReady(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "empty subnet",
|
name: "empty subnet",
|
||||||
node: &Node{
|
node: &Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: externalIP.IP}, Port: DefaultKiloPort},
|
Endpoint: wireguard.NewEndpoint(externalIP.IP, DefaultKiloPort),
|
||||||
InternalIP: internalIP,
|
InternalIP: internalIP,
|
||||||
Key: []byte{},
|
Key: wgtypes.Key{},
|
||||||
},
|
},
|
||||||
ready: false,
|
ready: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid",
|
name: "valid",
|
||||||
node: &Node{
|
node: &Node{
|
||||||
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: externalIP.IP}, Port: DefaultKiloPort},
|
Endpoint: wireguard.NewEndpoint(externalIP.IP, DefaultKiloPort),
|
||||||
InternalIP: internalIP,
|
InternalIP: internalIP,
|
||||||
Key: []byte{},
|
Key: key,
|
||||||
LastSeen: time.Now().Unix(),
|
LastSeen: time.Now().Unix(),
|
||||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
||||||
},
|
},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user