Skip to content

Commit

Permalink
Create default passwords when dev mode is set. #441 (#442)
Browse files Browse the repository at this point in the history
Signed-off-by: cmoulliard <[email protected]>
  • Loading branch information
cmoulliard authored Jan 7, 2025
1 parent 57bedf9 commit 6c0467e
Show file tree
Hide file tree
Showing 21 changed files with 440 additions and 101 deletions.
1 change: 1 addition & 0 deletions api/v1alpha1/localbuild_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type BuildCustomizationSpec struct {
Port string `json:"port,omitempty"`
UsePathRouting bool `json:"usePathRouting,omitempty"`
SelfSignedCert string `json:"selfSignedCert,omitempty"`
StaticPassword bool `json:"staticPassword,omitempty"`
}

type LocalbuildSpec struct {
Expand Down
1 change: 1 addition & 0 deletions hack/argo-cd/argocd-cm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ metadata:
name: argocd-cm
data:
application.resourceTrackingMethod: annotation
accounts.developer: apiKey, login
timeout.reconciliation: 60s
resource.exclusions: |
- kinds:
Expand Down
12 changes: 12 additions & 0 deletions hack/argo-cd/argocd-rbac-dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/name: argocd-rbac-cm
app.kubernetes.io/part-of: argocd
name: argocd-rbac-cm
namespace: argocd
data:
policy.csv: |
p, role:developer, applications, *, *, allow
g, developer, role:developer
1 change: 1 addition & 0 deletions hack/argo-cd/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- https://raw.githubusercontent.com/argoproj/argo-cd/v2.10.7/manifests/install.yaml
- argocd-rbac-dev.yaml

patches:
- path: dex-server.yaml
Expand Down
3 changes: 2 additions & 1 deletion pkg/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,5 +284,6 @@ func isBuildCustomizationSpecEqual(s1, s2 v1alpha1.BuildCustomizationSpec) bool
s1.IngressHost == s2.IngressHost &&
s1.Port == s2.Port &&
s1.UsePathRouting == s2.UsePathRouting &&
s1.SelfSignedCert == s2.SelfSignedCert
s1.SelfSignedCert == s2.SelfSignedCert &&
s1.StaticPassword == s2.StaticPassword
}
4 changes: 4 additions & 0 deletions pkg/cmd/create/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
const (
recreateClusterUsage = "Delete cluster first if it already exists."
buildNameUsage = "Name for build (Prefix for kind cluster name, pod names, etc)."
devPasswordUsage = "Set the password \"developer\" for the admin user of the applications: argocd & gitea."
kubeVersionUsage = "Version of the kind kubernetes cluster to create."
extraPortsMappingUsage = "List of extra ports to expose on the docker container and kubernetes cluster as nodePort " +
"(e.g. \"22:32222,9090:39090,etc\")."
Expand All @@ -40,6 +41,7 @@ var (
// Flags
recreateCluster bool
buildName string
devPassword bool
kubeVersion string
extraPortsMapping string
kindConfigPath string
Expand Down Expand Up @@ -68,6 +70,7 @@ func init() {
CreateCmd.PersistentFlags().StringVar(&buildName, "build-name", "localdev", buildNameUsage)
CreateCmd.PersistentFlags().MarkDeprecated("build-name", "use --name instead.")
CreateCmd.PersistentFlags().StringVar(&buildName, "name", "localdev", buildNameUsage)
CreateCmd.PersistentFlags().BoolVar(&devPassword, "dev-password", false, devPasswordUsage)
CreateCmd.PersistentFlags().StringVar(&kubeVersion, "kube-version", "v1.30.3", kubeVersionUsage)
CreateCmd.PersistentFlags().StringVar(&extraPortsMapping, "extra-ports", "", extraPortsMappingUsage)
CreateCmd.PersistentFlags().StringVar(&kindConfigPath, "kind-config", "", kindConfigPathUsage)
Expand Down Expand Up @@ -145,6 +148,7 @@ func create(cmd *cobra.Command, args []string) error {
IngressHost: ingressHost,
Port: port,
UsePathRouting: pathRouting,
StaticPassword: devPassword,
},

CustomPackageDirs: absDirPaths,
Expand Down
8 changes: 2 additions & 6 deletions pkg/cmd/get/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/cnoe-io/idpbuilder/pkg/entity"
"github.com/cnoe-io/idpbuilder/pkg/printer"
"github.com/cnoe-io/idpbuilder/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/cnoe-io/idpbuilder/api/v1alpha1"
Expand Down Expand Up @@ -238,13 +239,8 @@ func getSecretsByCNOELabel(ctx context.Context, kubeClient client.Client, l labe
return secrets, kubeClient.List(ctx, &secrets, &opts)
}

func getSecretByName(ctx context.Context, kubeClient client.Client, ns, name string) (v1.Secret, error) {
s := v1.Secret{}
return s, kubeClient.Get(ctx, client.ObjectKey{Name: name, Namespace: ns}, &s)
}

func getCorePackageSecret(ctx context.Context, kubeClient client.Client, ns, name string) (v1.Secret, error) {
s, err := getSecretByName(ctx, kubeClient, ns, name)
s, err := util.GetSecretByName(ctx, kubeClient, ns, name)
if err != nil {
return v1.Secret{}, err
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/controllers/gitrepository/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ func (r *RepositoryReconciler) reconcileGitRepo(ctx context.Context, repo *v1alp
return ctrl.Result{}, fmt.Errorf("getting git provider credentials: %w", err)
}

if r.Config.StaticPassword {
creds.password = util.StaticPassword
}

err = provider.setProviderCredentials(ctx, repo, creds)
if err != nil {
return ctrl.Result{}, fmt.Errorf("setting git provider credentials: %w", err)
Expand Down
1 change: 1 addition & 0 deletions pkg/controllers/gitrepository/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func (g *giteaProvider) getProviderCredentials(ctx context.Context, repo *v1alph
if !ok {
return gitProviderCredentials{}, fmt.Errorf("%s key not found in secret %s in %s ns", giteaAdminPasswordKey, repo.Spec.SecretRef.Name, repo.Spec.SecretRef.Namespace)
}

return gitProviderCredentials{
username: string(username),
password: string(password),
Expand Down
1 change: 0 additions & 1 deletion pkg/controllers/localbuild/argo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package localbuild
import (
"context"
"embed"

"github.com/cnoe-io/idpbuilder/api/v1alpha1"
"github.com/cnoe-io/idpbuilder/globals"
"github.com/cnoe-io/idpbuilder/pkg/k8s"
Expand Down
4 changes: 2 additions & 2 deletions pkg/controllers/localbuild/argo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ func TestGetK8sInstallResources(t *testing.T) {
t.Fatalf("GetK8sInstallResources() error: %v", err)
}

if len(objs) != 58 {
t.Fatalf("Expected 58 Argo Install Resources, got: %d", len(objs))
if len(objs) != 59 {
t.Fatalf("Expected 59 Argo Install Resources, got: %d", len(objs))
}
}

Expand Down
212 changes: 212 additions & 0 deletions pkg/controllers/localbuild/controller.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package localbuild

import (
"bytes"
"code.gitea.io/sdk/gitea"
"context"
"encoding/json"
"fmt"
"io"
"k8s.io/apimachinery/pkg/types"
"net/http"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -41,6 +47,10 @@ const (
argoCDApplicationSetAnnotationKeyRefreshTrue = "true"
)

type ArgocdSession struct {
Token string `json:"token"`
}

type LocalbuildReconciler struct {
client.Client
Scheme *runtime.Scheme
Expand Down Expand Up @@ -91,6 +101,48 @@ func (r *LocalbuildReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
}

if r.Config.StaticPassword {
logger.V(1).Info("static password is enabled")

// Check if the Argocd Initial admin secret exists
argocdInitialAdminPassword, err := r.extractArgocdInitialAdminSecret(ctx)
if err != nil {
// Argocd initial admin secret is not yet available ...
return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil
}

logger.V(1).Info("Initial argocd admin secret found ...")

// Secret containing the initial argocd password exists
// Lets try to update the password
if argocdInitialAdminPassword != "" {
err = r.updateArgocdPassword(ctx, argocdInitialAdminPassword)
if err != nil {
return ctrl.Result{}, err
} else {
logger.V(1).Info(fmt.Sprintf("Argocd admin password change succeeded !"))
}
}

// Check if the Gitea credentials secret exists
giteaAdminPassword, err := r.extractGiteaAdminSecret(ctx)
if err != nil {
// Gitea admin secret is not yet available ...
return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil
}
logger.V(1).Info("Gitea admin secret found ...")
// Secret containing the gitea password exists
// Lets try to update the password
if giteaAdminPassword != "" {
err = r.updateGiteaPassword(ctx, giteaAdminPassword)
if err != nil {
return ctrl.Result{}, err
} else {
logger.V(1).Info(fmt.Sprintf("Gitea admin password change succeeded !"))
}
}
}

logger.V(1).Info("done installing core packages. passing control to argocd")
_, err = r.ReconcileArgoAppsWithGitea(ctx, req, &localBuild)
if err != nil {
Expand Down Expand Up @@ -610,6 +662,166 @@ func (r *LocalbuildReconciler) requestArgoCDAppSetRefresh(ctx context.Context) e
return nil
}

func (r *LocalbuildReconciler) extractArgocdInitialAdminSecret(ctx context.Context) (string, error) {
sec := util.ArgocdInitialAdminSecretObject()
err := r.Client.Get(ctx, types.NamespacedName{
Namespace: sec.GetNamespace(),
Name: sec.GetName(),
}, &sec)

if err != nil {
if k8serrors.IsNotFound(err) {
return "", fmt.Errorf("initial admin secret not found")
}
}
return string(sec.Data["password"]), nil
}

func (r *LocalbuildReconciler) extractGiteaAdminSecret(ctx context.Context) (string, error) {
sec := util.GiteaAdminSecretObject()
err := r.Client.Get(ctx, types.NamespacedName{
Namespace: sec.GetNamespace(),
Name: sec.GetName(),
}, &sec)

if err != nil {
if k8serrors.IsNotFound(err) {
return "", fmt.Errorf("gitea admin secret not found")
}
}
return string(sec.Data["password"]), nil
}

func (r *LocalbuildReconciler) updateGiteaPassword(ctx context.Context, adminPassword string) error {
client, err := gitea.NewClient(util.GiteaBaseUrl(r.Config), gitea.SetHTTPClient(util.GetHttpClient()),
gitea.SetBasicAuth("giteaAdmin", adminPassword), gitea.SetContext(ctx),
)
if err != nil {
return fmt.Errorf("cannot create gitea client: %w", err)
}

opts := gitea.EditUserOption{
LoginName: "giteaAdmin",
Password: util.StaticPassword,
}

resp, err := client.AdminEditUser("giteaAdmin", opts)
if err != nil {
return fmt.Errorf("cannot update gitea admin user. status: %d error : %w", resp.StatusCode, err)
}

err = util.PatchPasswordSecret(ctx, r.Client, r.Config, util.GiteaNamespace, util.GiteaAdminSecret, util.GiteaAdminName, util.StaticPassword)
if err != nil {
return fmt.Errorf("patching the gitea credentials failed : %w", err)
}
return nil
}

func (r *LocalbuildReconciler) updateArgocdPassword(ctx context.Context, adminPassword string) error {
argocdEndpoint := util.ArgocdBaseUrl(r.Config) + "/api/v1"

payload := map[string]string{
"username": "admin",
"password": adminPassword,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("Error creating JSON payload: %v\n", err)
}

// Create an HTTP POST request to get the Session token
req, err := http.NewRequest("POST", argocdEndpoint+"/session", bytes.NewBuffer(payloadBytes))
if err != nil {
return fmt.Errorf("Error creating HTTP request: %v\n", err)
}
req.Header.Set("Content-Type", "application/json")

// Create an HTTP c and disable TLS verification
c := util.GetHttpClient()

// Send the request
resp, err := c.Do(req)
if err != nil {
return fmt.Errorf("Error sending request: %v\n", err)
}
defer resp.Body.Close()

// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("Error reading response body: %v\n", err)
}

// We got a session Token, so we can update the Argocd admin password
if resp.StatusCode == 200 {
var argocdSession ArgocdSession

err := json.Unmarshal([]byte(body), &argocdSession)
if err != nil {
return fmt.Errorf("Error unmarshalling JSON: %v", err)
}

payload := map[string]string{
"name": "admin",
"currentPassword": adminPassword,
"newPassword": util.StaticPassword,
}

payloadBytes, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("Error creating JSON payload: %v\n", err)
}

req, err := http.NewRequest("PUT", argocdEndpoint+"/account/password", bytes.NewBuffer(payloadBytes))
if req != nil {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", argocdSession.Token))
req.Header.Set("Content-Type", "application/json")
}

resp, err := c.Do(req)
if err != nil {
return fmt.Errorf("Error sending request: %v\n", err)
}
defer resp.Body.Close()

// Lets checking the new admin password
payload = map[string]string{
"username": "admin",
"password": util.StaticPassword,
}
payloadBytes, err = json.Marshal(payload)
if err != nil {
return fmt.Errorf("Error creating JSON payload: %v\n", err)
}

// Define the request able to verify if the username and password changed works
req, err = http.NewRequest("POST", argocdEndpoint+"/session", bytes.NewBuffer(payloadBytes))
if err != nil {
return fmt.Errorf("Error creating HTTP request: %v\n", err)
}
req.Header.Set("Content-Type", "application/json")

// Send the request
resp, err = c.Do(req)
if err != nil {
return fmt.Errorf("Error sending request: %v\n", err)
}
defer resp.Body.Close()

// Password verification succeeded !
if resp.StatusCode == 200 {
// Let's patch the existing secret now
err = util.PatchPasswordSecret(ctx, r.Client, r.Config, util.ArgocdNamespace, util.ArgocdInitialAdminSecretName, util.ArgocdAdminName, util.StaticPassword)
if err != nil {
return fmt.Errorf("patching the argocd initial secret failed : %w", err)
}
return nil
}
}
// No session token has been received and by consequence the admin password has not been changed
return nil
}

func (r *LocalbuildReconciler) applyArgoCDAnnotation(ctx context.Context, obj client.Object, argoCDType, annotationKey, annotationValue string) error {
annotations := obj.GetAnnotations()
if annotations != nil {
Expand Down
Loading

0 comments on commit 6c0467e

Please sign in to comment.