Skip to content

Commit

Permalink
Add teleport
Browse files Browse the repository at this point in the history
  • Loading branch information
tuladhar committed Jun 6, 2023
1 parent 3607ddd commit f9c80cb
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 17 deletions.
18 changes: 14 additions & 4 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ import (

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"

"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

"github.com/giantswarm/teleport-operator/internal/controller"
"github.com/giantswarm/teleport-operator/internal/pkg/teleportclient"
capi "sigs.k8s.io/cluster-api/api/v1beta1"
//+kubebuilder:scaffold:imports
)
Expand All @@ -52,11 +53,13 @@ func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
var namespace string
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
flag.StringVar(&namespace, "namespace", "", "Namespace where operator is deployed")
opts := zap.Options{
Development: true,
}
Expand Down Expand Up @@ -89,10 +92,17 @@ func main() {
os.Exit(1)
}

teleportClient, err := teleportclient.New(namespace)
if err != nil {
setupLog.Error(err, "unable to create teleport client")
os.Exit(1)
}

if err = (&controller.ClusterReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Cluster"),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Cluster"),
Scheme: mgr.GetScheme(),
TeleportClient: teleportClient,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Cluster")
os.Exit(1)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/giantswarm/teleport-operator
go 1.19

require (
github.com/giantswarm/microerror v0.4.0
github.com/go-logr/logr v1.2.4
github.com/gravitational/teleport/api v0.0.0-20230606022908-5e60f9001626
github.com/onsi/ginkgo/v2 v2.9.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/giantswarm/microerror v0.4.0 h1:QeU+UZL0rRlVXKqYOHMxS0L7g8UD+dn84NT7myWVh4U=
github.com/giantswarm/microerror v0.4.0/go.mod h1:Ju1YdC6TX/8witv7fIlkgiRr5FQUNyq3f4TX2QYnO7c=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
Expand Down
51 changes: 38 additions & 13 deletions internal/controller/cluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package controller
import (
"context"
"fmt"
"time"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
Expand All @@ -29,9 +30,8 @@ import (
capi "sigs.k8s.io/cluster-api/api/v1beta1"
ctrl "sigs.k8s.io/controller-runtime"

"github.com/giantswarm/teleport-operator/pkg/teleportclient"
"github.com/giantswarm/teleport-operator/internal/pkg/teleportclient"

"github.com/gravitational/teleport/api/client"
"sigs.k8s.io/controller-runtime/pkg/client"
)

Expand Down Expand Up @@ -59,6 +59,11 @@ type ClusterReconciler struct {
func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithValues("cluster", req.NamespacedName)

if _, err := r.TeleportClient.GetClient(ctx); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get Teleport client: %w", err)
}
log.Info("Teleport client connected")

cluster := &capi.Cluster{}
if err := r.Client.Get(ctx, req.NamespacedName, cluster); err != nil {
if apierrors.IsNotFound(err) {
Expand All @@ -68,20 +73,13 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
return ctrl.Result{}, err
}

log.Info("Found cluster")

// Skip if teleport secret already exists for the cluster
secretName := "teleport-kube-agent-join-token"
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: cluster.Namespace,
},
StringData: map[string]string{
"joinToken": "join-token-here",
},
}

secretNamespacedName := types.NamespacedName{
Name: secretName,
Namespace: cluster.Namespace,
Expand All @@ -92,10 +90,14 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
if apierrors.IsNotFound(err) {
log.Info(fmt.Sprintf("Secret does not exist: %s", secretName))
// Generate token from Teleport

// Here you can add the code to create the Secret
joinToken, err := r.TeleportClient.GetToken(ctx, cluster.Name)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to generate join token: %w", err)
}
log.Info("Join token generated")
secret.StringData = map[string]string{
"joinToken": "short-lived-join-token-goes-here",
"joinToken": joinToken,
}
if err := r.Create(ctx, secret); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to create Secret: %w", err)
Expand All @@ -107,11 +109,34 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
return ctrl.Result{}, fmt.Errorf("failed to get Secret: %w", err)
}
} else {
log.Info(fmt.Sprintf("Secret exists: %s. Cluster will be ignored", secretName))
log.Info(fmt.Sprintf("Secret exists: %s", secretName))
// Update secret if token expired or is expiring
hasExpired, err := r.TeleportClient.HasTokenExpired(ctx, cluster.Name)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to verify token expiry: %w", err)
}
if hasExpired {
log.Info("Join token expired")
joinToken, err := r.TeleportClient.GetToken(ctx, cluster.Name)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to generate teleport token: %w", err)
}
log.Info("Join token generated")
secret.StringData = map[string]string{
"joinToken": joinToken,
}
if err := r.Update(ctx, secret); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to update Secret: %w", err)
} else {
log.Info(fmt.Sprintf("Secret updated: %s", secretName))
}
} else {
log.Info("Join token is valid, nothing to do.")
}
}

// Here you can add the code to handle the case where the Secret exists
return ctrl.Result{}, nil
return ctrl.Result{RequeueAfter: 1 * time.Minute}, nil
}

// SetupWithManager sets up the controller with the Manager.
Expand Down
19 changes: 19 additions & 0 deletions internal/pkg/teleportclient/random.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package teleportclient

import (
"time"

"k8s.io/apimachinery/pkg/util/rand"
)

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")

func randSeq(n int) string {
rand.Seed(time.Now().UnixNano())

b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
152 changes: 152 additions & 0 deletions internal/pkg/teleportclient/teleportclient.go
Original file line number Diff line number Diff line change
@@ -1 +1,153 @@
package teleportclient

import (
"context"
"fmt"
"time"

"github.com/giantswarm/microerror"
tc "github.com/gravitational/teleport/api/client"
tt "github.com/gravitational/teleport/api/types"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)

type TeleportClient struct {
ProxyAddr string
IdentityFile string
}

const TELEPORT_JOIN_TOKEN_VALIDITY = 1 * time.Hour

func New(namespace string) (*TeleportClient, error) {
// Get a config to talk to the apiserver
cfg, err := config.GetConfig()
if err != nil {
fmt.Println("unable to get config to talk to the apiserver:", err)
return nil, err
}

// Create a new client
c, err := client.New(cfg, client.Options{})
if err != nil {
fmt.Println("unable to create a new client:", err)
return nil, err
}

// Check if the Secret exists
secret := &corev1.Secret{}
secretNamespacedName := types.NamespacedName{
Name: "teleport-operator",
Namespace: namespace, // Replace with the correct namespace
}
if err := c.Get(context.Background(), secretNamespacedName, secret); err != nil {
return nil, err
}

proxyAddrBytes, proxyAddrOk := secret.Data["proxyAddr"]
identityFileBytes, identityFileOk := secret.Data["identityFile"]
if !proxyAddrOk && !identityFileOk {
return nil, fmt.Errorf("malformed Secret: `identityFile` or `proxyAddr` key not found")
}
identityFile := string(identityFileBytes)
proxyAddr := string(proxyAddrBytes)

return &TeleportClient{
IdentityFile: identityFile,
ProxyAddr: proxyAddr,
}, nil
}

func (t *TeleportClient) GetClient(ctx context.Context) (*tc.Client, error) {
c, err := tc.New(ctx, tc.Config{
Addrs: []string{
t.ProxyAddr,
},
Credentials: []tc.Credentials{
tc.LoadIdentityFileFromString(t.IdentityFile),
},
})

if err != nil {
return nil, microerror.Mask(err)
}

_, err = c.Ping(ctx)
if err != nil {
return nil, microerror.Mask(err)
}

return c, nil
}

func (t *TeleportClient) GetToken(ctx context.Context, clusterName string) (string, error) {
clt, err := t.GetClient(ctx)
if err != nil {
return "", microerror.Mask(err)
}

// Look for an existing token or generate one
{
tokens, err := clt.GetTokens(ctx)
if err != nil {
return "", microerror.Mask(err)
}

for _, t := range tokens {
if t.GetMetadata().Labels["cluster"] == clusterName {
err = clt.DeleteToken(ctx, t.GetName())
if err != nil {
return "", microerror.Mask(err)
}
break
}
}

// Generate a token
expiration := time.Now().Add(TELEPORT_JOIN_TOKEN_VALIDITY)

token := randSeq(32)
newToken, err := tt.NewProvisionToken(token, []tt.SystemRole{tt.RoleKube, tt.RoleNode}, expiration)
if err != nil {
return "", microerror.Mask(err)
}
oldMeta := newToken.GetMetadata()
oldMeta.Labels = map[string]string{
"cluster": clusterName,
}
newToken.SetMetadata(oldMeta)
err = clt.UpsertToken(ctx, newToken)
if err != nil {
return "", microerror.Mask(err)
}

return token, nil
}
}

func (t *TeleportClient) HasTokenExpired(ctx context.Context, clusterName string) (bool, error) {
clt, err := t.GetClient(ctx)
if err != nil {
return false, microerror.Mask(err)
}

{
tokens, err := clt.GetTokens(ctx)
if err != nil {
return false, microerror.Mask(err)
}

for _, t := range tokens {
if t.GetMetadata().Labels["cluster"] == clusterName {
// Nearing expiry, less than an hour, refresh it
// if time.Since(*t.GetMetadata().Expires).Hours() < -1 {
// return true, nil
// }
return false, nil
}
}
return true, nil
}
}

0 comments on commit f9c80cb

Please sign in to comment.