Skip to content

Commit

Permalink
auth service
Browse files Browse the repository at this point in the history
This is a first iteration over auth service
A new http service is added, when contacted it creates a new role for the
given clusterID
  • Loading branch information
aleoli committed Nov 16, 2020
1 parent 4e013cc commit 8b670a4
Show file tree
Hide file tree
Showing 15 changed files with 685 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
- pod-mutator
- peering-request-webhook-init
- crd-replicator
- auth-service
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
Expand Down
11 changes: 11 additions & 0 deletions build/auth-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM golang:1.14 as builder
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go
COPY . /go/src/github.com/liqotech/liqo
WORKDIR /go/src/github.com/liqotech/liqo
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./cmd/auth-service/
RUN cp auth-service /usr/bin/auth-service

FROM scratch
COPY --from=builder /usr/bin/auth-service /usr/bin/auth-service
ENTRYPOINT [ "/usr/bin/auth-service" ]
38 changes: 38 additions & 0 deletions cmd/auth-service/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"flag"
auth_service "github.com/liqotech/liqo/internal/auth-service"
"k8s.io/klog"
"os"
"path/filepath"
"time"
)

func main() {
klog.Info("Starting")

var namespace string
var kubeconfigPath string
var resyncSeconds int64
var listeningPort string

flag.StringVar(&namespace, "namespace", "default", "Namespace where your configs are stored.")
flag.StringVar(&kubeconfigPath, "kubeconfigPath", filepath.Join(os.Getenv("HOME"), ".kube", "config"), "For debug purpose, set path to local kubeconfig")
flag.Int64Var(&resyncSeconds, "resyncSeconds", 30, "Resync seconds for the informers")
flag.StringVar(&listeningPort, "listeningPort", "5000", "Sets the port where the service will listen")
flag.Parse()

klog.Info("Namespace: ", namespace)

authService, err := auth_service.NewAuthServiceCtrl(namespace, kubeconfigPath, time.Duration(resyncSeconds)*time.Second)
if err != nil {
klog.Error(err)
os.Exit(1)
}

if err = authService.Start(listeningPort); err != nil {
klog.Error(err)
os.Exit(1)
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/grandcat/zeroconf v1.0.0
github.com/gruntwork-io/terratest v0.30.7
github.com/joho/godotenv v1.3.0
github.com/julienschmidt/httprouter v1.3.0
github.com/miekg/dns v1.1.27
github.com/mitchellh/go-homedir v1.1.0
github.com/ozgio/strutil v0.3.0
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
Expand Down
117 changes: 117 additions & 0 deletions internal/auth-service/auth-service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package auth_service

import (
"context"
"github.com/julienschmidt/httprouter"
discoveryv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1"
"github.com/liqotech/liqo/pkg/crdClient"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/klog"
"net/http"
"strings"
"time"
)

type AuthServiceCtrl struct {
namespace string
clientset kubernetes.Interface
saInformer cache.SharedIndexInformer
nodeInformer cache.SharedIndexInformer
secretInformer cache.SharedIndexInformer
}

func NewAuthServiceCtrl(namespace string, kubeconfigPath string, resyncTime time.Duration) (*AuthServiceCtrl, error) {
config, err := crdClient.NewKubeconfig(kubeconfigPath, &discoveryv1alpha1.GroupVersion)
if err != nil {
return nil, err
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}

informerFactory := informers.NewSharedInformerFactoryWithOptions(clientset, resyncTime, informers.WithNamespace(namespace))

saInformer := informerFactory.Core().V1().ServiceAccounts().Informer()
saInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{})

nodeInformer := informerFactory.Core().V1().Nodes().Informer()
nodeInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{})

secretInformer := informerFactory.Core().V1().Secrets().Informer()
secretInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{})

informerFactory.Start(wait.NeverStop)
informerFactory.WaitForCacheSync(wait.NeverStop)

return &AuthServiceCtrl{
namespace: namespace,
clientset: clientset,
saInformer: saInformer,
nodeInformer: nodeInformer,
secretInformer: secretInformer,
}, nil
}

func (authService *AuthServiceCtrl) Start(listeningPort string) error {
if err := authService.configureToken(); err != nil {
return err
}

router := httprouter.New()

router.POST("/role", authService.role)

err := http.ListenAndServe(strings.Join([]string{":", listeningPort}, ""), router)
if err != nil {
klog.Error(err)
return err
}
return nil
}

func (authService *AuthServiceCtrl) configureToken() error {
if err := authService.createToken(); err != nil {
return err
}

authService.secretInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
newSecret, ok := newObj.(*v1.Secret)
if !ok {
return
}
if newSecret.Name != AuthTokenSecretName {
return
}

if _, err := authService.getTokenFromSecret(newSecret); err != nil {
err := authService.clientset.CoreV1().Secrets(authService.namespace).Delete(context.TODO(), newSecret.Name, metav1.DeleteOptions{})
if err != nil {
klog.Error(err)
return
}
}
},
DeleteFunc: func(obj interface{}) {
newSecret, ok := obj.(*v1.Secret)
if !ok {
return
}
if newSecret.Name != AuthTokenSecretName {
return
}

if err := authService.createToken(); err != nil {
klog.Error(err)
return
}
},
})
return nil
}
91 changes: 91 additions & 0 deletions internal/auth-service/auth-token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package auth_service

import (
"context"
"crypto/rand"
"errors"
"fmt"
v1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog"
"strings"
)

const (
AuthTokenSecretName = "auth-token"
)

func (authService *AuthServiceCtrl) getToken() (string, error) {
obj, exists, err := authService.secretInformer.GetStore().GetByKey(strings.Join([]string{authService.namespace, AuthTokenSecretName}, "/"))
if err != nil {
klog.Error(err)
return "", err
} else if !exists {
err = kerrors.NewNotFound(schema.GroupResource{
Group: "v1",
Resource: "secrets",
}, AuthTokenSecretName)
klog.Error(err)
return "", err
}

secret, ok := obj.(*v1.Secret)
if !ok {
err = kerrors.NewNotFound(schema.GroupResource{
Group: "v1",
Resource: "secrets",
}, AuthTokenSecretName)
klog.Error(err)
return "", err
}

return authService.getTokenFromSecret(secret.DeepCopy())
}

func (authService *AuthServiceCtrl) createToken() error {
_, exists, _ := authService.secretInformer.GetStore().GetByKey(strings.Join([]string{authService.namespace, AuthTokenSecretName}, "/"))
if !exists {
token, err := generateToken()
if err != nil {
return err
}

secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: AuthTokenSecretName,
},
StringData: map[string]string{
"token": token,
},
}
_, err = authService.clientset.CoreV1().Secrets(authService.namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
if err != nil && !kerrors.IsAlreadyExists(err) {
klog.Error(err)
return err
}
}
return nil
}

func (authService *AuthServiceCtrl) getTokenFromSecret(secret *v1.Secret) (string, error) {
v, ok := secret.Data["token"]
if !ok {
// TODO: specialise secret type
err := errors.New("invalid secret")
klog.Error(err)
return "", err
}
return string(v), nil
}

func generateToken() (string, error) {
b := make([]byte, 64)
_, err := rand.Read(b)
if err != nil {
klog.Error(err)
return "", err
}
return fmt.Sprintf("%x", b), nil
}
39 changes: 39 additions & 0 deletions internal/auth-service/clusterRole.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package auth_service

import (
"context"
discoveryv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func (authService *AuthServiceCtrl) createClusterRole(remoteClusterId string, sa *v1.ServiceAccount) (*rbacv1.ClusterRole, error) {
role := &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: remoteClusterId,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "v1",
Kind: "ServiceAccount",
Name: sa.Name,
UID: sa.UID,
},
},
},
Rules: []rbacv1.PolicyRule{
{
APIGroups: []string{discoveryv1alpha1.GroupVersion.Group},
Resources: []string{"peeringrequests"},
Verbs: []string{"create"},
},
{
APIGroups: []string{discoveryv1alpha1.GroupVersion.Group},
Resources: []string{"peeringrequests"},
Verbs: []string{"get", "delete", "update"},
ResourceNames: []string{remoteClusterId},
},
},
}
return authService.clientset.RbacV1().ClusterRoles().Create(context.TODO(), role, metav1.CreateOptions{})
}
37 changes: 37 additions & 0 deletions internal/auth-service/clusterRoleBinding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package auth_service

import (
"context"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func (authService *AuthServiceCtrl) createClusterRoleBinding(remoteClusterId string, sa *v1.ServiceAccount, clusterRole *rbacv1.ClusterRole) (*rbacv1.ClusterRoleBinding, error) {
rb := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: remoteClusterId,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "v1",
Kind: "ServiceAccount",
Name: sa.Name,
UID: sa.UID,
},
},
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: sa.Name,
Namespace: sa.Namespace,
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.SchemeGroupVersion.Group,
Kind: "ClusterRole",
Name: clusterRole.Name,
},
}
return authService.clientset.RbacV1().ClusterRoleBindings().Create(context.TODO(), rb, metav1.CreateOptions{})
}

0 comments on commit 8b670a4

Please sign in to comment.