2021-05-20 08:38:11 +00:00
#!/usr/bin/env bash
2021-06-15 18:38:21 +00:00
export KUBECONFIG = "kind.yaml"
2021-05-20 08:38:11 +00:00
KIND_CLUSTER = "kind-cluster-kilo"
KIND_BINARY = " ${ KIND_BINARY :- kind } "
KUBECTL_BINARY = " ${ KUBECTL_BINARY :- kubectl } "
2021-06-15 18:38:21 +00:00
KGCTL_BINARY = " ${ KGCTL_BINARY :- kgctl } "
2021-05-20 08:38:11 +00:00
KILO_IMAGE = " ${ KILO_IMAGE :- squat /kilo } "
2021-06-15 10:54:27 +00:00
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
2021-06-15 18:38:21 +00:00
return 0
2021-06-15 10:54:27 +00:00
else
printf "%s(attempt %d/%d)\n" " $ERROR " " $c " " $COUNT " | color " $YELLOW "
if [ " $c " != " $COUNT " ] ; then
2021-06-15 18:38:21 +00:00
printf "retrying in %d seconds...\n" " $SLEEP " | color " $YELLOW "
sleep " $SLEEP "
2021-06-15 10:54:27 +00:00
fi
fi
done
return 1
}
2021-06-16 10:50:52 +00:00
_not( ) {
if " $@ " ; then
return 1
fi
return 0
}
2021-06-15 18:38:21 +00:00
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 true " $1 "
}
delete_interface( ) {
docker rm --force " $1 "
}
create_peer( ) {
cat <<EOF | $KUBE CTL_BINARY apply -f -
apiVersion: kilo.squat.ai/v1alpha1
kind: Peer
metadata:
name: $1
spec:
allowedIPs:
- $2
persistentKeepalive: $3
publicKey: $4
EOF
}
delete_peer( ) {
$KUBECTL_BINARY delete peer " $1 "
}
2021-05-20 08:38:11 +00:00
is_ready( ) {
2021-05-20 21:36:14 +00:00
for pod in $( $KUBECTL_BINARY -n " $1 " get pods -o name -l " $2 " ) ; do
2021-06-15 18:38:21 +00:00
if ! $KUBECTL_BINARY -n " $1 " get " $pod " | tail -n 1 | grep -q Running; then
2021-05-20 08:38:11 +00:00
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( ) {
2021-05-20 21:36:14 +00:00
block_until_ready " $1 " " app.kubernetes.io/name= $2 "
2021-05-20 08:38:11 +00:00
}
# Blocks until all pods of a deployment are ready.
block_until_ready( ) {
2021-06-15 10:54:27 +00:00
retry 30 5 " some $2 pods are not ready yet " is_ready " $1 " " $2 "
2021-05-20 08:38:11 +00:00
}
# Set up the kind cluster and deploy Kilo, Adjacency and a helper with curl.
setup_suite( ) {
$KIND_BINARY delete clusters $KIND_CLUSTER > /dev/null
# Create the kind cluster.
$KIND_BINARY create cluster --name $KIND_CLUSTER --config ./kind-config.yaml
# Load the Kilo image into kind.
2021-05-20 21:36:14 +00:00
docker tag " $KILO_IMAGE " squat/kilo:test
2021-05-20 08:38:11 +00:00
$KIND_BINARY load docker-image squat/kilo:test --name $KIND_CLUSTER
2021-06-15 18:38:21 +00:00
# Create the kubeconfig secret.
$KUBECTL_BINARY create secret generic kubeconfig --from-file= kubeconfig = " $KUBECONFIG " -n kube-system
2021-05-20 08:38:11 +00:00
# Apply Kilo the the cluster.
2021-06-14 07:08:46 +00:00
$KUBECTL_BINARY apply -f ../manifests/crds.yaml
2021-05-20 08:38:11 +00:00
$KUBECTL_BINARY apply -f kilo-kind-userspace.yaml
block_until_ready_by_name kube-system kilo-userspace
$KUBECTL_BINARY wait nodes --all --for= condition = Ready
2021-06-16 10:50:52 +00:00
# Wait for CoreDNS.
2021-05-20 08:38:11 +00:00
block_until_ready kube_system k8s-app= kube-dns
2021-06-16 10:50:52 +00:00
# Ensure the curl helper is not scheduled on a control-plane node.
$KUBECTL_BINARY apply -f helper-curl.yaml
block_until_ready_by_name default curl
2021-05-20 08:38:11 +00:00
$KUBECTL_BINARY taint node $KIND_CLUSTER -control-plane node-role.kubernetes.io/master:NoSchedule-
$KUBECTL_BINARY apply -f https://raw.githubusercontent.com/heptoprint/adjacency/master/example.yaml
block_until_ready_by_name adjacency adjacency
2021-06-16 10:50:52 +00:00
}
curl_pod( ) {
$KUBECTL_BINARY get pods -l app.kubernetes.io/name= curl -o name | xargs -I{ } " $KUBECTL_BINARY " exec { } -- /bin/sh -c " curl $* "
2021-05-20 08:38:11 +00:00
}
2021-06-15 10:54:27 +00:00
check_ping( ) {
2021-06-15 18:38:21 +00:00
local LOCAL
while [ $# -gt 0 ] ; do
case $1 in
--local)
LOCAL = true
; ;
esac
shift
done
for ip in $( $KUBECTL_BINARY 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
2021-06-16 10:50:52 +00:00
ping = $( curl_pod -m 1 -s http://" $ip " :8080/ping)
2021-06-15 18:38:21 +00:00
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
2021-05-20 08:38:11 +00:00
}
check_adjacent( ) {
2021-06-15 18:38:21 +00:00
$KUBECTL_BINARY get pods -l app.kubernetes.io/name= curl -o name | xargs -I{ } " $KUBECTL_BINARY " exec { } -- /bin/sh -c 'curl -m 1 -s adjacency:8080/?format=fancy'
2021-06-16 10:50:52 +00:00
[ " $( curl_pod -m 1 -s adjacency:8080/?format= json | jq | grep -c true ) " -eq " $1 " ]
2021-05-20 08:38:11 +00:00
}
2021-06-15 18:38:21 +00:00
check_peer( ) {
local INTERFACE = $1
local PEER = $2
local ALLOWED_IP = $3
local GRANULARITY = $4
create_interface " $INTERFACE "
docker run --rm --entrypoint= /usr/bin/wg " $KILO_IMAGE " genkey > " $INTERFACE "
2021-06-16 08:37:17 +00:00
assert " create_peer $PEER $ALLOWED_IP 10 $( docker run --rm --entrypoint= /bin/sh -v " $PWD / $INTERFACE " :/key " $KILO_IMAGE " -c 'cat /key | wg pubkey' ) " "should be able to create Peer"
assert " $KGCTL_BINARY 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 $KILO_IMAGE setconf $INTERFACE /peer.ini " "should be able to apply configuration from kgctl"
2021-06-15 18:38:21 +00:00
docker run --rm --network= host --cap-add= NET_ADMIN --entrypoint= /usr/bin/wg -v /var/run/wireguard:/var/run/wireguard -v " $PWD / $INTERFACE " :/key " $KILO_IMAGE " set " $INTERFACE " private-key /key
docker run --rm --network= host --cap-add= NET_ADMIN --entrypoint= /sbin/ip " $KILO_IMAGE " address add " $ALLOWED_IP " dev " $INTERFACE "
docker run --rm --network= host --cap-add= NET_ADMIN --entrypoint= /sbin/ip " $KILO_IMAGE " link set " $INTERFACE " up
docker run --rm --network= host --cap-add= NET_ADMIN --entrypoint= /sbin/ip " $KILO_IMAGE " route add 10.42/16 dev " $INTERFACE "
2021-06-16 08:37:17 +00:00
assert "retry 10 5 '' check_ping --local" "should be able to ping Pods from host"
2021-06-15 18:38:21 +00:00
rm " $INTERFACE " " $PEER " .ini
delete_peer " $PEER "
delete_interface " $INTERFACE "
}
2021-05-20 08:38:11 +00:00
test_locationmesh( ) {
2021-05-21 09:09:14 +00:00
# shellcheck disable=SC2016
2021-06-15 18:38:21 +00:00
$KUBECTL_BINARY 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"]}]}}}}'
2021-05-20 08:38:11 +00:00
block_until_ready_by_name kube-system kilo-userspace
$KUBECTL_BINARY wait pod -l app.kubernetes.io/name= adjacency --for= condition = Ready --timeout 3m
2021-06-16 08:37:17 +00:00
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 12" "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 12" "adjacency should return the right number of successful pings after reconciling"
2021-05-20 08:38:11 +00:00
}
2021-06-15 18:38:21 +00:00
test_locationmesh_peer( ) {
check_peer wg1 e2e 10.5.0.1/32 location
}
2021-05-20 08:38:11 +00:00
test_fullmesh( ) {
2021-05-21 09:09:14 +00:00
# shellcheck disable=SC2016
2021-06-15 18:38:21 +00:00
$KUBECTL_BINARY 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"]}]}}}}'
2021-05-20 08:38:11 +00:00
block_until_ready_by_name kube-system kilo-userspace
$KUBECTL_BINARY wait pod -l app.kubernetes.io/name= adjacency --for= condition = Ready --timeout 3m
2021-06-16 08:37:17 +00:00
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 12" "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 12" "adjacency should return the right number of successful pings after reconciling"
2021-05-20 08:38:11 +00:00
}
2021-06-15 18:38:21 +00:00
test_fullmesh_peer( ) {
check_peer wg1 e2e 10.5.0.1/32 full
}
2021-06-16 08:46:22 +00:00
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"
}
2021-06-16 10:50:52 +00:00
test_fullmesh_allowed_location_ips( ) {
docker exec kind-cluster-kilo-control-plane ip address add 10.6.0.1/32 dev eth0
$KUBECTL_BINARY 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_BINARY 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
}
2021-05-20 08:38:11 +00:00
teardown_suite ( ) {
$KIND_BINARY delete clusters $KIND_CLUSTER
}