Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DANM 4.0 EP1: Validating Webhook #82

Merged
merged 6 commits into from
May 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions cmd/webhook/webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"flag"
"log"
"strconv"
"time"
"crypto/tls"
"net/http"
"github.com/nokia/danm/pkg/netadmit"
)

func main() {
cert := flag.String("tls-cert-bundle", "", "File containing the x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert).")
key := flag.String("tls-private-key-file", "", "File containing the x509 private key matching --tls-cert-bundle.")
port := flag.Int("bind-port", 8443, "The port on which to serve. Default is 8443.")
address := flag.String("bind-address", "", "The IP address on which to listen. Default is all interfaces.")
flag.Parse()
if cert == nil || key == nil {
log.Println("ERROR: Configuring TLS is mandatory, --tls-cert-bundle and --tls-private-key-file cannot be empty!")
return
}
tlsConf, err := tls.LoadX509KeyPair(*cert, *key)
if err != nil {
log.Println("ERROR: TLS configuration could not be initialized, because:" + err.Error())
return
}
http.HandleFunc("/webhook", netadmit.ValidateNetwork)
server := &http.Server{
Addr: *address + ":" + strconv.Itoa(*port),
TLSConfig: &tls.Config{Certificates: []tls.Certificate{tlsConf}},
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
}
log.Println("INFO:DANM webhook is about to start listening on " + *address + ":" + strconv.Itoa(*port))
err = server.ListenAndServeTLS("", "")
log.Fatal(err)
}
3 changes: 1 addition & 2 deletions crd/apis/danm/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ type DanmNet struct {
type DanmNetSpec struct {
NetworkID string `json:"NetworkID"`
NetworkType string `json:"NetworkType,omitempty"`
Options DanmNetOption `json:"Options"`
Validation bool `json:"Validation,omitempty"`
Options DanmNetOption `json:"Options,omitempty"`
}

type DanmNetOption struct {
Expand Down
4 changes: 2 additions & 2 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 4 additions & 10 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,13 @@ package: github.com/nokia/danm
ignore:
- github.com/vishvananda/netlink
- github.com/nokia/danm/pkg/bitarray
- github.com/nokia/danm/pkg/bitarray_test
- github.com/nokia/danm/pkg/cnidel
- github.com/nokia/danm/pkg/cnidel_test
- github.com/nokia/danm/pkg/crd
- github.com/nokia/danm/pkg/danm
- github.com/nokia/danm/pkg/danmep
- github.com/nokia/danm/pkg/danmnet
- github.com/nokia/danm/pkg/ipam
- github.com/nokia/danm/pkg/ipam_test
- github.com/nokia/danm/pkg/stubs
- github.com/nokia/danm/pkg/metacni
- github.com/nokia/danm/pkg/netadmit
- github.com/nokia/danm/pkg/netcontrol
- github.com/nokia/danm/pkg/svccontrol
- github.com/nokia/danm/pkg/syncher
- github.com/nokia/danm/pkg/netwatcher
- github.com/nokia/danm/pkg/svcwatcher
import:
- package: k8s.io/client-go
version: kubernetes-1.11.1
Expand Down
43 changes: 43 additions & 0 deletions integration/docker/webhook/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
FROM alpine:3.9
MAINTAINER Levente Kale <[email protected]>

ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
ENV GOOS=linux

WORKDIR /

RUN mkdir -p $GOPATH/bin \
&& mkdir -p $GOPATH/src

RUN apk add --no-cache libcap iputils

RUN apk add --no-cache --virtual .tools ca-certificates gcc musl-dev go glide git tar curl \
&& mkdir -p $GOPATH/src/github.com/nokia/danm \
&& git clone -b 'webhook' --depth 1 https://github.com/nokia/danm.git $GOPATH/src/github.com/nokia/danm \
&& cd $GOPATH/src/github.com/nokia/danm \
&& glide install --strip-vendor \
&& go get -d github.com/vishvananda/netlink \
&& go get github.com/containernetworking/plugins/pkg/ns \
&& go get github.com/golang/groupcache/lru \
&& rm -rf $GOPATH/src/k8s.io/code-generator \
&& git clone -b 'kubernetes-1.13.4' --depth 1 https://github.com/kubernetes/code-generator.git $GOPATH/src/k8s.io/code-generator \
&& go install k8s.io/code-generator/cmd/deepcopy-gen \
&& go install k8s.io/code-generator/cmd/client-gen \
&& go install k8s.io/code-generator/cmd/lister-gen \
&& go install k8s.io/code-generator/cmd/informer-gen \
&& deepcopy-gen --alsologtostderr --input-dirs github.com/nokia/danm/crd/apis/danm/v1 -O zz_generated.deepcopy --bounding-dirs github.com/nokia/danm/crd/apis \
&& client-gen --alsologtostderr --clientset-name versioned --input-base "" --input github.com/nokia/danm/crd/apis/danm/v1 --clientset-path github.com/nokia/danm/crd/client/clientset \
&& lister-gen --alsologtostderr --input-dirs github.com/nokia/danm/crd/apis/danm/v1 --output-package github.com/nokia/danm/crd/client/listers \
&& informer-gen --alsologtostderr --input-dirs github.com/nokia/danm/crd/apis/danm/v1 --versioned-clientset-package github.com/nokia/danm/crd/client/clientset/versioned --listers-package github.com/nokia/danm/crd/client/listers --output-package github.com/nokia/danm/crd/client/informers \
&& go install -a -ldflags '-extldflags "-static"' github.com/nokia/danm/cmd/webhook \
&& cp $GOPATH/bin/webhook /usr/local/bin/webhook \
&& rm -rf $GOPATH/src \
&& rm -rf $GOPATH/bin \
&& apk del .tools \
&& rm -rf /var/cache/apk/* \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /tmp/* \
&& rm -rf ~/.glide

ENTRYPOINT ["/usr/local/bin/webhook"]
2 changes: 1 addition & 1 deletion integration/manifests/netwatcher/netwatcher_ds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ spec:
hostPID: true
containers:
- name: netwatcher
image: netwatcher:3.0.0
image: netwatcher
securityContext:
capabilities:
add:
Expand Down
3 changes: 2 additions & 1 deletion integration/manifests/svcwatcher/svcwatcher_ds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ spec:
template:
metadata:
annotations:
# Adapt to your own network environment!
danm.k8s.io/interfaces: |
[
{
Expand All @@ -25,7 +26,7 @@ spec:
"node-role.kubernetes.io/master": ""
containers:
- name: svcwatcher
image: svcwatcher:3.0.0
image: svcwatcher
args:
- "--logtostderr"
tolerations:
Expand Down
76 changes: 76 additions & 0 deletions integration/manifests/webhook/webhook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: danm-webhook-config
namespace: kube-system
webhooks:
- name: danm-webhook.nokia.k8s.io
clientConfig:
service:
name: danm-webhook-svc
namespace: kube-system
path: "/webhook"
# Configure your pre-generated certificate matching the details of your environment
caBundle: <CA_BUNDLE>
rules:
- operations: ["CREATE","UPDATE"]
apiGroups: ["danm.k8s.io"]
apiVersions: ["v1"]
resources: ["danmnets"]
failurePolicy: Fail
---
apiVersion: v1
kind: Service
metadata:
name: danm-webhook-svc
namespace: kube-system
labels:
danm: webhook
spec:
ports:
- name: webhook
port: 443
targetPort: 8443
selector:
danm: webhook
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: danm-webhook-deployment
namespace: kube-system
labels:
danm: webhook
spec:
selector:
matchLabels:
danm: webhook
template:
metadata:
annotations:
# Adapt to your own network environment!
danm.k8s.io/interfaces: |
[
{
"network":"flannel"
}
]
name: danm-webhook
labels:
danm: webhook
spec:
containers:
- name: danm-webhook
image: danm_webhook
command: [ "/usr/local/bin/webhook", "-tls-cert-bundle=/etc/webhook/certs/danm_webhook.crt", "-tls-private-key-file=/etc/webhook/certs/danm_webhook.key", "bind-port=8443" ]
imagePullPolicy: IfNotPresent
volumeMounts:
- name: webhook-certs
mountPath: /etc/webhook/certs
readOnly: true
# Configure the directory holding the Webhook's server certificates
volumes:
- name: webhook-certs
hostPath:
path: /etc/kubernetes/ssl/
21 changes: 20 additions & 1 deletion pkg/bitarray/bitarray.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package bitarray

import (
b64 "encoding/base64"
"errors"
"math"
"net"
b64 "encoding/base64"
)

const (
MaxSupportedNetmask = 32
)

// BitArray is type to represent an arbitrary long array of bits
Expand Down Expand Up @@ -32,6 +38,19 @@ func NewBitArrayFromBase64(text string) *BitArray {
return arr
}

func CreateBitArrayFromIpnet(ipnet *net.IPNet) (*BitArray,error) {
ones, _ := ipnet.Mask.Size()
if ones > MaxSupportedNetmask {
return nil, errors.New("DANM does not support networks with more than 2^32 IP addresses")
}
bitArray,err := NewBitArray(int(math.Pow(2,float64(MaxSupportedNetmask-ones))))
if err != nil {
return nil,errors.New("BitArray allocation failed because:" + err.Error())
}
bitArray.Set(uint32(math.Pow(2,float64(MaxSupportedNetmask-ones))-1))
return bitArray,nil
}

// Set sets the bit at the input position of the BitArray
func (arr *BitArray) Set(pos uint32) {
arr.data[pos/8] |= byte( 0x1 << (7-pos%8))
Expand Down
54 changes: 41 additions & 13 deletions pkg/ipam/ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"time"
"encoding/binary"
"math/big"
"math/rand"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -22,9 +23,6 @@ import (
// The reserved IP address is represented by setting a bit in the network's BitArray type allocation matrix
// The refreshed DanmNet object is modified in the K8s API server at the end
func Reserve(danmClient danmclientset.Interface, netInfo danmtypes.DanmNet, req4, req6 string) (string, string, string, error) {
if netInfo.Spec.Validation != true {
return "", "", "", errors.New("Invalid network: " + netInfo.ObjectMeta.Name)
}
tempNetSpec := netInfo
netClient := danmClient.DanmV1().DanmNets(netInfo.ObjectMeta.Namespace)
for {
Expand Down Expand Up @@ -87,13 +85,13 @@ func updateDanmNetAllocation (netClient client.DanmNetInterface, netInfo danmtyp
func resetIP(netInfo *danmtypes.DanmNet, rip string) {
ba := bitarray.NewBitArrayFromBase64(netInfo.Spec.Options.Alloc)
_, ipnet, _ := net.ParseCIDR(netInfo.Spec.Options.Cidr)
ipnetNum := netcontrol.Ip2int(ipnet.IP)
ipnetNum := Ip2int(ipnet.IP)
ip, _, err := net.ParseCIDR(rip)
if err != nil {
//Invalid IP, nothing to do here. Next call would crash if we wouldn't return
return
}
reserved := netcontrol.Ip2int(ip)
reserved := Ip2int(ip)
if !ipnet.Contains(ip) {
//IP is outside of CIDR, nothing to do here. Next call would crash if we wouldn't return
return
Expand Down Expand Up @@ -132,13 +130,13 @@ func allocIPv4(reqType string, netInfo *danmtypes.DanmNet, ip4 *string) (error)
}
ba := bitarray.NewBitArrayFromBase64(netInfo.Spec.Options.Alloc)
_, ipnet, _ := net.ParseCIDR(netInfo.Spec.Options.Cidr)
ipnetNum := netcontrol.Ip2int(ipnet.IP)
begin := netcontrol.Ip2int(net.ParseIP(netInfo.Spec.Options.Pool.Start)) - ipnetNum
end := netcontrol.Ip2int(net.ParseIP(netInfo.Spec.Options.Pool.End)) - ipnetNum
ipnetNum := Ip2int(ipnet.IP)
begin := Ip2int(net.ParseIP(netInfo.Spec.Options.Pool.Start)) - ipnetNum
end := Ip2int(net.ParseIP(netInfo.Spec.Options.Pool.End)) - ipnetNum
for i:=begin; i<=end; i++ {
if !ba.Get(uint32(i)) {
ones, _ := ipnet.Mask.Size()
*ip4 = (netcontrol.Int2ip(ipnetNum + i)).String() + "/" + strconv.Itoa(ones)
*ip4 = (Int2ip(ipnetNum + i)).String() + "/" + strconv.Itoa(ones)
ba.Set(uint32(i))
netInfo.Spec.Options.Alloc = ba.Encode()
break
Expand All @@ -160,8 +158,8 @@ func allocIPv4(reqType string, netInfo *danmtypes.DanmNet, ip4 *string) (error)
return errors.New("static ip is not part of network CIDR/allocation pool")
}
ba := bitarray.NewBitArrayFromBase64(netInfo.Spec.Options.Alloc)
ipnetNum := netcontrol.Ip2int(ipnetFromNet.IP)
requested := netcontrol.Ip2int(ip)
ipnetNum := Ip2int(ipnetFromNet.IP)
requested := Ip2int(ip)
if ba.Get(requested - ipnetNum) {
return errors.New("requested fix ip address is already in use")
}
Expand Down Expand Up @@ -189,12 +187,12 @@ func allocIPv6(reqType string, netInfo *danmtypes.DanmNet, ip6 *string, macAddr
bigeui.SetString(eui, 16)
ip6addr, ip6net, _ := net.ParseCIDR(net6)
ss := big.NewInt(0)
ss.Add(netcontrol.Ip62int(ip6addr), bigeui)
ss.Add(Ip62int(ip6addr), bigeui)
maskLen, _ := ip6net.Mask.Size()
if maskLen>64 {
return errors.New("IPv6 subnets smaller than /64 are not supported at the moment!")
}
*ip6 = (netcontrol.Int2ip6(ss)).String() + "/" + strconv.Itoa(maskLen)
*ip6 = (Int2ip6(ss)).String() + "/" + strconv.Itoa(maskLen)
} else {
net6 := netInfo.Spec.Options.Net6
if net6 == "" {
Expand Down Expand Up @@ -222,4 +220,34 @@ func generateMac()(string) {
func GarbageCollectIps(danmClient danmclientset.Interface, netInfo *danmtypes.DanmNet, ip4, ip6 string) {
Free(danmClient, *netInfo, ip4)
Free(danmClient, *netInfo, ip6)
}

// Ip2int converts an IP address stored according to the Golang net package to a native Golang big endian, 32-bit integer
func Ip2int(ip net.IP) uint32 {
if len(ip) == 16 {
return binary.BigEndian.Uint32(ip[12:16])
}
return binary.BigEndian.Uint32(ip)
}

// Ip62int converts an IPv6 address stored according to the Golang net package to a native Golang big endian, 64-bit integer
func Ip62int(ip6 net.IP) *big.Int {
Ip6Int := big.NewInt(0)
Ip6Int.SetBytes(ip6.To16())
return Ip6Int
}

// Int2ip converts an IP address stored as a native Golang big endian, 32-bit integer to an IP
// represented according to the Golang net package
func Int2ip(nn uint32) net.IP {
ip := make(net.IP, 4)
binary.BigEndian.PutUint32(ip, nn)
return ip
}

// Int2ip6 converts an IP address stored as a native Golang big endian, 64-bit integer to an IP
// represented according to the Golang net package
func Int2ip6(nn *big.Int) net.IP {
ip := nn.Bytes()
return ip
}
Loading