Skip to content

Commit 77af5c9

Browse files
committed
Merge pull request 'feat: add kubeaid project token generation for role kubeaid-agent' (#234) from integration/project-token-for-kubeaid-agent into main
Reviewed-on: https://gitea.obmondo.com/EnableIT/kubeaid-bootstrap-script/pulls/234
2 parents 408cbfd + e4feb5b commit 77af5c9

File tree

6 files changed

+121
-113
lines changed

6 files changed

+121
-113
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ outputs/
99
temp/
1010
robot-ssh
1111
*.pub
12+
13+
kubeaid-bootstrap-script

pkg/constants/constants.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package constants
22

3-
import "path"
3+
import (
4+
"path"
5+
"time"
6+
)
47

58
// Environment variable names.
69
const (
@@ -100,16 +103,26 @@ var (
100103
const (
101104
ReleaseNameArgoCD = "argocd"
102105

103-
ArgoCDProjectKubeAid = "kubeaid"
106+
ArgoCDProjectKubeAid = "kubeaid"
107+
ArgoCDRoleKubeAidAgent = "kubeaid-agent"
104108

105109
// Apps.
110+
ArgoCDAppArgoCD = "argocd"
106111
ArgoCDAppRoot = "root"
107112
ArgoCDAppCapiCluster = "capi-cluster"
108113
ArgoCDAppHetznerRobot = "hetzner-robot"
109114
ArgoCDAppClusterAutoscaler = "cluster-autoscaler"
110115
ArgoCDAppVelero = "velero"
111116
ArgoCDAppKubePrometheus = "kube-prometheus"
112117
ArgoCDExternalSnapshotter = "external-snapshotter"
118+
119+
ArgoCDProjectRolePolicyFmt = "p, proj:%s:%s, %s, %s, %s/*, %s" // Inputs: project-name, role-name, resource, action, project-name, effect
120+
ArgoCDLabelKeyManagedBy = "kubeaid.io/managed-by"
121+
122+
ArgoCDRBACEffectAllow = "allow"
123+
ArgoCDRBACEffectDeny = "deny"
124+
125+
ArgoCDProjectRoleSecretName = "argocd-project-role-kubeaid-agent"
113126
)
114127

115128
// Sealed Secrets.
@@ -190,3 +203,9 @@ const (
190203

191204
GzippedFilenameSuffix = ".gz"
192205
)
206+
207+
// Time durations
208+
const (
209+
OneDay = 24 * time.Hour
210+
OneMonth = 30 * OneDay
211+
)
Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1 @@
1-
argo-cd:
2-
configs:
3-
cm:
4-
# For ArgoCD-CrossPlane integration, we need to use annotation based application resource
5-
# tracking.
6-
#
7-
# You ask why? Let me explain :
8-
#
9-
# Suppose, we define an XR claim (which is namespace scoped) in our git repository. The
10-
# 'infrastructure' ArgoCD App is tracking this XR Claim.
11-
# The XR Claim will dynamically generate an XR (which is cluster scoped). And this XR will
12-
# derive the 'argocd.argoproj.io/instance' label from its parent XR Claim.
13-
#
14-
# So, the situation is : we have a dynamically generated XR, not defined in git, but being
15-
# tracked by ArgoCD, because of that derived label.
16-
# This will cause the 'infrastructure' ArgoCD App to be always out of sync. Additionally,
17-
# if someone syncs the 'infrastructure' ArgoCD App, with pruning enabled, then ArgoCD will
18-
# delete those XRs.
19-
application.resourceTrackingMethod: annotation
20-
21-
{{- if and .ObmondoConfig .ObmondoConfig.Monitoring }}
22-
accounts.kubeaid-agent: apiKey
23-
{{- end }}
24-
25-
{{- if and .ObmondoConfig .ObmondoConfig.Monitoring }}
26-
rbac:
27-
policy.csv: |
28-
p, role:kubeaid-agent, applications, get, kubeaid/*, allow
29-
p, role:kubeaid-agent, applications, sync, kubeaid/*, allow
30-
p, role:kubeaid-agent, applications, health, kubeaid/*, allow
31-
p, role:kubeaid-agent, applications, status, kubeaid/*, allow
32-
33-
g, kubeaid-agent, role:kubeaid-agent
34-
{{- end }}
1+
---

pkg/utils/kubernetes/argo.go

Lines changed: 85 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,16 @@ import (
66
"log/slog"
77
"os"
88
"path"
9-
"strconv"
109
"strings"
1110
"time"
1211

1312
"github.com/argoproj/argo-cd/v2/pkg/apiclient"
14-
"github.com/argoproj/argo-cd/v2/pkg/apiclient/account"
1513
"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
1614
"github.com/argoproj/argo-cd/v2/pkg/apiclient/certificate"
1715
"github.com/argoproj/argo-cd/v2/pkg/apiclient/project"
1816
"github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
1917
argoCDV1Aplha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
18+
"github.com/argoproj/argo-cd/v2/util/rbac"
2019
"github.com/aws/aws-sdk-go-v2/aws"
2120
"google.golang.org/grpc/codes"
2221
"google.golang.org/grpc/status"
@@ -71,14 +70,6 @@ func InstallAndSetupArgoCD(ctx context.Context, clusterDir string, clusterClient
7170

7271
Namespace: constants.NamespaceArgoCD,
7372
ReleaseName: constants.ReleaseNameArgoCD,
74-
75-
Values: map[string]any{
76-
"argo-cd": map[string]any{
77-
"configs": map[string]any{
78-
"cm": getArgoCDConfigMapOptions(),
79-
},
80-
},
81-
},
8273
})
8374

8475
// Port-forward ArgoCD and create ArgoCD client.
@@ -123,44 +114,13 @@ func InstallAndSetupArgoCD(ctx context.Context, clusterDir string, clusterClient
123114
if (config.ParsedGeneralConfig.Obmondo != nil) &&
124115
(config.ParsedGeneralConfig.Obmondo.Monitoring) {
125116

126-
argoCDAccountClientCloser, argoCDAccountClient := argoCDClient.NewAccountClientOrDie()
127-
defer argoCDAccountClientCloser.Close()
117+
projectClientCloser, projectClient := argoCDClient.NewProjectClientOrDie()
118+
defer projectClientCloser.Close()
128119

129-
setupKubeAgentArgoCDAccount(ctx, argoCDAccountClient, clusterClient)
120+
setupKubeAgentArgoCDProjectRole(ctx, projectClient, clusterClient)
130121
}
131122
}
132123

133-
func getArgoCDConfigMapOptions() map[string]any {
134-
argoCDConfigMapOptions := map[string]any{
135-
/*
136-
For ArgoCD-CrossPlane integration, we need to use annotation based application resource
137-
tracking.
138-
139-
You ask why? Let me explain :
140-
141-
Suppose, we define an XR claim (which is namespace scoped) in our git repository. The
142-
'infrastructure' ArgoCD App is tracking this XR Claim.
143-
The XR Claim will dynamically generate an XR (which is cluster scoped). And this XR
144-
will derive the 'argocd.argoproj.io/instance' label from its parent XR Claim.
145-
146-
So, the situation is : we have a dynamically generated XR, not defined in git, but
147-
being tracked by ArgoCD, because of that derived label.
148-
This will cause the 'infrastructure' ArgoCD App to be always out of sync. Additionally,
149-
if someone syncs the 'infrastructure' ArgoCD App, with pruning enabled, then ArgoCD
150-
will delete those XRs.
151-
*/
152-
"application.resourceTrackingMethod": "annotation",
153-
}
154-
155-
// When the user is an Obmondo customer, KubeAid Agent will get deployed to the cluster.
156-
// We need to create an ArgoCD account for KubeAid Agent.
157-
if config.ParsedGeneralConfig.Obmondo != nil {
158-
argoCDConfigMapOptions["accounts.kubeaid-agent"] = "apiKey"
159-
}
160-
161-
return argoCDConfigMapOptions
162-
}
163-
164124
// Port-forwards the ArgoCD server and creates an ArgoCD client.
165125
// Returns the ArgoCD client.
166126
func NewArgoCDClient(ctx context.Context, clusterClient client.Client) apiclient.Client {
@@ -275,9 +235,17 @@ func SyncAllArgoCDApps(ctx context.Context) {
275235
assert.AssertErrNil(ctx, err, "Failed listing ArgoCD apps")
276236

277237
for _, item := range response.Items {
238+
// Skip syncing argocd ArgoCD app while syncing other ArgoCD apps.
239+
if item.Name == constants.ArgoCDAppArgoCD {
240+
continue
241+
}
242+
278243
SyncArgoCDApp(ctx, item.Name, []*argoCDV1Aplha1.SyncOperationResource{})
279244
}
280245
}
246+
247+
// Lastly, sync the argocd ArgoCD app.
248+
SyncArgoCDApp(ctx, constants.ArgoCDAppArgoCD, []*argoCDV1Aplha1.SyncOperationResource{})
281249
}
282250

283251
// Syncs the ArgoCD App (if not synced already).
@@ -441,55 +409,104 @@ func isArgoCDAppSynced(
441409
}
442410
}
443411

444-
func setupKubeAgentArgoCDAccount(ctx context.Context,
445-
argoCDAccountServiceClient account.AccountServiceClient,
412+
func setupKubeAgentArgoCDProjectRole(ctx context.Context,
413+
projectClient project.ProjectServiceClient,
446414
clusterClient client.Client,
447415
) {
448-
// During Helm installation, an ArgoCD account for KubeAid Agent got created.
449-
// Unfortunately, ArgoCD will not auto-generate an initial password for that account.
450-
// So, we need to do it ourselves. And save it in the 'argocd-initial-kubeaid-agent-secret'
451-
// Kubernetes Secret, from where KubeAid Agent can pick it up.
452-
453-
slog.InfoContext(ctx, "Setting up KubeAid Agent ArgoCD account")
416+
// We'll create a project token for the 'kubeaid-agent' role.
417+
// And save it in the 'argocd-project-role-kubeaid-agent' Kubernetes Secret with token
418+
// from where KubeAid Agent can pick it up.
419+
slog.InfoContext(ctx, "Setting up KubeAid Agent ArgoCD project role")
454420

455-
// Generate a random password.
456-
password := strconv.Itoa(int(time.Now().Unix()))
421+
projectQuery := &project.ProjectQuery{
422+
Name: constants.ArgoCDProjectKubeAid,
423+
}
457424

458-
// Use it for the KubeAid Agent user.
459-
_, err := argoCDAccountServiceClient.UpdatePassword(ctx, &account.UpdatePasswordRequest{
460-
Name: "kubeaid-agent",
461-
CurrentPassword: getArgoCDAdminPassword(ctx, clusterClient),
462-
NewPassword: password,
463-
})
425+
// Fetch 'kubeaid' project details
426+
kubeAidProject, err := projectClient.Get(ctx, projectQuery)
464427
assert.AssertErrNil(ctx, err,
465-
"Failed generating initial password for KubeAid Agent ArgoCD account",
428+
"Failed fetching KubeAid project details",
466429
)
467430

468-
// Store it in the 'argocd-initial-kubeaid-agent-secret' Kubernetes Secret.
431+
description := "Role kubeaid-agent to perform necessary operations via KubeAid Agent"
432+
policies := []string{
433+
getKubeAidAgentRolePolicy(
434+
rbac.ResourceApplications,
435+
rbac.ActionGet,
436+
constants.ArgoCDRBACEffectAllow,
437+
),
438+
getKubeAidAgentRolePolicy(
439+
rbac.ResourceApplications,
440+
rbac.ActionSync,
441+
constants.ArgoCDRBACEffectAllow,
442+
),
443+
}
444+
projectRole := argoCDV1Aplha1.ProjectRole{
445+
Name: constants.ArgoCDRoleKubeAidAgent,
446+
Description: description,
447+
Policies: policies,
448+
Groups: []string{constants.ArgoCDRoleKubeAidAgent},
449+
}
450+
kubeAidProject.Spec.Roles = append(kubeAidProject.Spec.Roles, projectRole)
469451

470-
secretName := "argocd-initial-kubeaid-agent-secret"
452+
// Update the project 'kubeaid' by adding role 'kubeaid-agent' details
453+
projectRequest := &project.ProjectUpdateRequest{
454+
Project: kubeAidProject,
455+
}
456+
_, err = projectClient.Update(ctx, projectRequest)
457+
assert.AssertErrNil(ctx, err,
458+
"Failed updating KubeAid project with KubeAid Agent role details",
459+
)
471460

472-
argoCDInitialKubeAidAgentSecret := &coreV1.Secret{
461+
// Generate the 'kubeaid-agent' project token with no expiry.
462+
// KubeAid Agent is then uses this token to perform sync operations.
463+
tokenRequest := &project.ProjectTokenCreateRequest{
464+
Project: constants.ArgoCDProjectKubeAid,
465+
Role: constants.ArgoCDRoleKubeAidAgent,
466+
}
467+
tokenResponse, err := projectClient.CreateToken(ctx, tokenRequest)
468+
assert.AssertErrNil(ctx, err,
469+
"Failed generating KubeAid project token for KubeAid Agent role",
470+
)
471+
472+
// Store it in the 'argocd-project-role-kubeaid-agent' Kubernetes Secret.
473+
// We adding a label to identify the secret was created by 'kubeaid-bootstrap-script'.
474+
secretObj := &coreV1.Secret{
473475
ObjectMeta: metaV1.ObjectMeta{
474-
Name: secretName,
476+
Name: constants.ArgoCDProjectRoleSecretName,
475477
Namespace: constants.NamespaceArgoCD,
478+
Labels: map[string]string{
479+
constants.ArgoCDLabelKeyManagedBy: constants.ArgoCDProjectKubeAid,
480+
},
476481
},
477482

478483
StringData: map[string]string{
479-
"password": password,
484+
"token": tokenResponse.GetToken(),
480485
},
481486
}
482-
err = clusterClient.Create(ctx, argoCDInitialKubeAidAgentSecret, &client.CreateOptions{})
487+
err = clusterClient.Create(ctx, secretObj, &client.CreateOptions{})
483488
if k8sAPIErrors.IsAlreadyExists(err) {
484489
return
485490
}
486491
assert.AssertErrNil(ctx, err,
487492
"Failed creating Kubernetes Secret",
488-
slog.String("secret", secretName),
493+
slog.String("secret", constants.ArgoCDProjectRoleSecretName),
489494
slog.String("namespace", constants.NamespaceArgoCD),
490495
)
491496
}
492497

498+
func getKubeAidAgentRolePolicy(resource, action, effect string) string {
499+
return fmt.Sprintf(
500+
constants.ArgoCDProjectRolePolicyFmt,
501+
constants.ArgoCDProjectKubeAid,
502+
constants.ArgoCDRoleKubeAidAgent,
503+
resource,
504+
action,
505+
constants.ArgoCDProjectKubeAid,
506+
effect,
507+
)
508+
}
509+
493510
// Returns the initial ArgoCD admin password.
494511
func getArgoCDAdminPassword(ctx context.Context, clusterClient client.Client) string {
495512
argoCDInitialAdminSecret := &coreV1.Secret{}

pkg/utils/kubernetes/helm.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"helm.sh/helm/v3/pkg/action"
1010
"helm.sh/helm/v3/pkg/chart/loader"
1111
"helm.sh/helm/v3/pkg/cli"
12+
"helm.sh/helm/v3/pkg/cli/values"
13+
"helm.sh/helm/v3/pkg/getter"
1214
"helm.sh/helm/v3/pkg/release"
1315

1416
"github.com/Obmondo/kubeaid-bootstrap-script/pkg/utils/assert"
@@ -19,7 +21,7 @@ type HelmInstallArgs struct {
1921
ChartPath,
2022
ReleaseName,
2123
Namespace string
22-
Values map[string]interface{}
24+
Values *values.Options
2325
}
2426

2527
// Installs the Helm chart (if not already deployed), present at the given local path.
@@ -65,6 +67,14 @@ func HelmInstall(ctx context.Context, args *HelmInstallArgs) {
6567

6668
// Load and install the Helm chart.
6769
{
70+
// Load the custom values into map[string]any
71+
valuesMap := make(map[string]any)
72+
if args.Values != nil {
73+
p := getter.All(settings)
74+
valuesMap, err = args.Values.MergeValues(p)
75+
assert.AssertErrNil(ctx, err, "Failed merging the Helm chart values")
76+
}
77+
6878
// Load Helm chart from the local chart path.
6979
chart, err := loader.Load(args.ChartPath)
7080
assert.AssertErrNil(ctx, err,
@@ -82,7 +92,7 @@ func HelmInstall(ctx context.Context, args *HelmInstallArgs) {
8292
installAction.Timeout = 10 * time.Minute
8393
installAction.Wait = true
8494

85-
_, err = installAction.Run(chart, args.Values)
95+
_, err = installAction.Run(chart, valuesMap)
8696
assert.AssertErrNil(ctx, err, "Failed installing Helm chart")
8797
}
8898
}

pkg/utils/kubernetes/sealed_secrets.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,6 @@ func InstallSealedSecrets(ctx context.Context) {
2424
ChartPath: path.Join(utils.GetKubeAidDir(), "argocd-helm-charts/sealed-secrets"),
2525
Namespace: constants.NamespaceSealedSecrets,
2626
ReleaseName: "sealed-secrets",
27-
Values: map[string]any{
28-
"sealed-secrets": map[string]any{
29-
"namespace": constants.NamespaceSealedSecrets,
30-
"fullnameOverride": "sealed-secrets-controller",
31-
},
32-
"backup": map[string]any{},
33-
},
3427
})
3528
}
3629

0 commit comments

Comments
 (0)