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

Implement CI bot token for Tekton #182

Closed
wants to merge 13 commits into from
6 changes: 6 additions & 0 deletions helm/teleport-operator/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ data:
managementClusterName: {{ .Values.teleport.managementClusterName | quote }}
proxyAddr: {{ .Values.teleport.proxyAddr | quote }}
teleportVersion: {{ .Values.teleport.teleportVersion | quote }}

{{- if .Values.ciBot.enabled }}
test.proxyAddr: {{ .Values.ciBot.proxyAddr | quote }}
test.managementClusterName: {{ .Values.ciBot.managementClusterName | quote }}
test.teleportVersion: {{ default .Values.teleport.teleportVersion .Values.ciBot.teleportVersion | quote }}
{{- end }}
3 changes: 3 additions & 0 deletions helm/teleport-operator/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ spec:
{{- if .Values.tbot.enabled }}
- "--tbot"
{{- end }}
{{- if .Values.ciBot.enabled }}
- "--enable-ci-bot"
{{- end }}
ports:
- name: metrics
protocol: TCP
Expand Down
24 changes: 24 additions & 0 deletions helm/teleport-operator/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,30 @@
}
}
},
"ciBot": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": false,
"description": "Enable CI bot token generation for test teleport instance"
},
"proxyAddr": {
"type": "string",
"description": "Teleport proxy address for test instance"
},
"managementClusterName": {
"type": "string",
"description": "Management cluster name for test instance"
},
"teleportVersion": {
"type": "string",
"description": "Optional: Teleport version for test instance. Defaults to main teleport version if not set"
}
},
"required": ["enabled"],
"additionalProperties": false
},
"tbotDeployment": {
"type": "object",
"properties": {
Expand Down
8 changes: 7 additions & 1 deletion helm/teleport-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ teleport:
teleportClusterName: test.teleport.giantswarm.io
teleportVersion: 16.1.7


pod:
user:
id: 1000
Expand Down Expand Up @@ -68,6 +67,13 @@ affinity:
tbot:
enabled: false

# Enables `--enable-ci-bot` flag, To be used on clusters that Tekton pipelines are running
ciBot:
enabled: true
proxyAddr: "test.teleport.giantswarm.io:443"
managementClusterName: "test.teleport.giantswarm.io"
teleportVersion: ""

# Enables `teleport-operator-tbot` deployment
tbotDeployment:
enabled: true
35 changes: 34 additions & 1 deletion internal/controller/cluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type ClusterReconciler struct {
Teleport *teleport.Teleport
IsBotEnabled bool
Namespace string
EnableCIBot bool
lastAssignedRoles []string
}

Expand Down Expand Up @@ -74,6 +75,34 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct

log.Info("Reconciling cluster", "cluster", cluster)

// Handle CI Bot token generation first
if r.EnableCIBot && req.Namespace == "giantswarm" {
log.Info("Processing CI Bot token generation",
"namespace", req.Namespace,
"clusterName", cluster.Name,
"testClientInitialized", r.Teleport.TestClient != nil,
"testInstanceEnabled", r.Teleport.Config.TestInstance != nil && r.Teleport.Config.TestInstance.Enabled,
)

if r.Teleport.TestClient == nil {
log.Info("Test client not initialized, skipping CI bot token generation")
return ctrl.Result{RequeueAfter: time.Minute * 5}, nil
}

if r.Teleport.Config.TestInstance == nil || !r.Teleport.Config.TestInstance.Enabled {
log.Info("Test instance not configured or enabled, skipping CI bot token generation")
return ctrl.Result{RequeueAfter: time.Minute * 5}, nil
}

if err := r.Teleport.GenerateCIBotToken(ctx, log, "ci-bot"); err != nil {
log.Error(err, "Failed to generate CI bot token")
return ctrl.Result{RequeueAfter: time.Minute * 5}, microerror.Mask(err)
}

log.Info("Successfully processed CI bot token")
return ctrl.Result{RequeueAfter: time.Hour}, nil
}

appsEnabled, err := r.Teleport.AreTeleportAppsEnabled(ctx, cluster.Name, cluster.Namespace)
if err != nil {
log.Error(err, "Failed to check if Teleport apps are enabled")
Expand Down Expand Up @@ -254,11 +283,15 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct

// We need to requeue to check the teleport token validity
// and update secret for the cluster, if it expires
return ctrl.Result{RequeueAfter: 5 * time.Minute}, nil
requeueAfter := 5 * time.Minute
return ctrl.Result{RequeueAfter: requeueAfter}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
if r.EnableCIBot {
r.Log.Info("Setting up controller with CI Bot enabled")
}
return ctrl.NewControllerManagedBy(mgr).
For(&capi.Cluster{}).
Complete(r)
Expand Down
33 changes: 31 additions & 2 deletions internal/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,21 @@ import (
"github.com/giantswarm/teleport-operator/internal/pkg/key"
)

type TeleportInstance struct {
ProxyAddr string
TeleportVersion string
ManagementClusterName string
Enabled bool
}

type Config struct {
ProxyAddr string
TeleportVersion string
ManagementClusterName string
AppName string
AppVersion string
AppCatalog string
TestInstance *TeleportInstance
}

func GetConfigFromConfigMap(ctx context.Context, ctrlClient client.Client, namespace string) (*Config, error) {
Expand Down Expand Up @@ -60,14 +68,35 @@ func GetConfigFromConfigMap(ctx context.Context, ctrlClient client.Client, names
return nil, microerror.Mask(err)
}

return &Config{
config := &Config{
ProxyAddr: proxyAddr,
TeleportVersion: teleportVersion,
ManagementClusterName: managementClusterName,
AppName: appName,
AppVersion: appVersion,
AppCatalog: appCatalog,
}, nil
}

testProxyAddr, err := getConfigMapString(configMap, "test.proxyAddr")
if err == nil {
testClusterName, err := getConfigMapString(configMap, "test.managementClusterName")
if err != nil {
return nil, microerror.Mask(err)
}
testVersion, err := getConfigMapString(configMap, "test.teleportVersion")
if err != nil {
testVersion = config.TeleportVersion
}

config.TestInstance = &TeleportInstance{
ProxyAddr: testProxyAddr,
TeleportVersion: testVersion,
ManagementClusterName: testClusterName,
Enabled: true,
}
}

return config, nil
}

func getConfigMapString(configMap *corev1.ConfigMap, key string) (string, error) {
Expand Down
49 changes: 49 additions & 0 deletions internal/pkg/teleport/teleport.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

"github.com/giantswarm/microerror"
"github.com/go-logr/logr"
"gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
Expand All @@ -16,21 +17,69 @@ import (

type Teleport struct {
Config *config.Config
PrimaryClient Client
TestClient Client
Identity *config.IdentityConfig
TeleportClient Client
Namespace string
TokenGenerator token.Generator
Client client.Client
Log logr.Logger
}

func New(namespace string, cfg *config.Config, tokenGenerator token.Generator) *Teleport {
return &Teleport{
Config: cfg,
Namespace: namespace,
TokenGenerator: tokenGenerator,
Log: logr.Discard(),
}
}

func (t *Teleport) WithLog(log logr.Logger) *Teleport {
t.Log = log
return t
}

func (t *Teleport) InitializeClients(ctx context.Context) error {
var err error

// Initialize primary client first
primaryIdentity, err := config.GetIdentityConfigFromSecret(ctx, t.Client, t.Namespace)
if err != nil {
return microerror.Mask(err)
}

t.PrimaryClient, err = NewClient(ctx, t.Config.ProxyAddr, primaryIdentity.IdentityFile)
if err != nil {
return microerror.Mask(err)
}

// Initialize test client if configured - but don't fail if it can't connect
if t.Config.TestInstance != nil && t.Config.TestInstance.Enabled {
t.Log.Info("Attempting to initialize test client",
"proxyAddr", t.Config.TestInstance.ProxyAddr)

testIdentity, err := config.GetIdentityConfigFromSecret(ctx, t.Client, t.Namespace)
if err != nil {
t.Log.Info("Failed to get test instance identity", "error", err)
return nil
}

t.TestClient, err = NewClient(ctx, t.Config.TestInstance.ProxyAddr, testIdentity.IdentityFile)
if err != nil {
t.Log.Info("Failed to initialize test client",
"error", err,
"proxyAddr", t.Config.TestInstance.ProxyAddr)
return nil // Don't fail the operator if test client fails
}

t.Log.Info("Successfully initialized test client")
}

return nil
}

func (t *Teleport) AreTeleportAppsEnabled(ctx context.Context, clusterName, namespace string) (bool, error) {
configMap := &corev1.ConfigMap{}
err := t.Client.Get(ctx, types.NamespacedName{
Expand Down
91 changes: 91 additions & 0 deletions internal/pkg/teleport/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ package teleport

import (
"context"
"fmt"
"strings"
"time"

"github.com/go-logr/logr"
"github.com/gravitational/teleport/api/types"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/giantswarm/microerror"

Expand Down Expand Up @@ -97,3 +103,88 @@ func (t *Teleport) DeleteToken(ctx context.Context, log logr.Logger, registerNam
}
return nil
}

func (t *Teleport) GenerateCIBotToken(ctx context.Context, log logr.Logger, name string) error {
log.Info("Attempting to generate CI bot token",
"testClientInitialized", t.TestClient != nil,
"testInstanceEnabled", t.Config.TestInstance != nil && t.Config.TestInstance.Enabled,
)

// Check test instance configuration
if t.Config.TestInstance == nil || !t.Config.TestInstance.Enabled {
log.Info("Test instance not configured or not enabled")
return nil
}

// Check test client
if t.TestClient == nil {
log.Info("Test client not initialized")
return nil
}

// Generate token
tokenName := fmt.Sprintf("ci-bot-%s", t.TokenGenerator.Generate())
token, err := types.NewProvisionToken(
tokenName,
[]types.SystemRole{types.RoleBot},
time.Now().Add(720*time.Hour),
)
if err != nil {
log.Error(err, "Failed to create provision token")
return err
}

// Set metadata
m := token.GetMetadata()
m.Labels = map[string]string{
"type": "ci-bot",
"created": time.Now().Format(time.RFC3339),
}
token.SetMetadata(m)

// Create token in test Teleport instance
if err := t.TestClient.UpsertToken(ctx, token); err != nil {
return microerror.Mask(err)
}

// Store in Kubernetes secret in giantswarm namespace
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "teleport-ci-token",
Namespace: "giantswarm",
Labels: map[string]string{
"app.kubernetes.io/name": "teleport-operator",
"app.kubernetes.io/component": "ci-bot",
"app.kubernetes.io/managed-by": "teleport-operator",
},
},
StringData: map[string]string{
"token": token.GetName(),
"proxy": t.Config.TestInstance.ProxyAddr,
},
}

// Create or update secret
existing := &corev1.Secret{}
err = t.Client.Get(ctx, client.ObjectKey{
Name: secret.Name,
Namespace: secret.Namespace,
}, existing)

if err != nil {
if k8serrors.IsNotFound(err) {
if err := t.Client.Create(ctx, secret); err != nil {
return microerror.Mask(err)
}
log.Info("Created CI bot token secret in giantswarm namespace")
return nil
}
return err
}

if err := t.Client.Update(ctx, secret); err != nil {
return microerror.Mask(err)
}
log.Info("Updated CI bot token secret in giantswarm namespace")
return nil
}
Loading