@@ -68,11 +68,30 @@ type DevOptions struct {
6868 HubHTTPSPort int
6969 HubHTTPPort int
7070 ImagePullPolicy string
71+
72+ // WithDex enables Dex as an embedded OIDC identity provider.
73+ // When true, Dex is deployed into the hub kind cluster and the hub is
74+ // configured with the Dex issuer URL automatically.
75+ WithDex bool
76+ DexHTTPPort int // host port for the Dex NodePort mapping (default 5556)
7177}
7278
7379// fallbackAssetVersion is used when unable to fetch the latest version
7480const fallbackAssetVersion = "0.0.1"
7581
82+ // Dex constants used when --with-dex is set.
83+ const (
84+ devDexIssuerURL = "http://dex.kedge-system.svc.cluster.local:5556/dex"
85+ devDexClientID = "kedge"
86+ devDexClientSecret = "kedge-test-secret"
87+ devDexChartRef = "dexidp/dex" // from https://charts.dexidp.io, added as a repo
88+ devDexChartVersion = "0.24.0"
89+ devDexReleaseName = "dex"
90+ devDexNodePort = 31556
91+ // bcrypt of "Password1!" for the dev Dex static user
92+ devDexUserHash = "$2a$10$ntVcHD0gEYObjVin2ti7XuMILVz0rTQl//HVPc3cR8z7AAVbQGrkO"
93+ )
94+
7695// gitHubRelease represents a GitHub release response
7796type gitHubRelease struct {
7897 TagName string `json:"tag_name"`
@@ -90,6 +109,7 @@ func NewDevOptions(streams genericclioptions.IOStreams) *DevOptions {
90109 APIServerPort : 6443 ,
91110 HubHTTPSPort : 8443 ,
92111 HubHTTPPort : 8080 ,
112+ DexHTTPPort : 5556 ,
93113 }
94114}
95115
@@ -108,6 +128,8 @@ func (o *DevOptions) AddCmdFlags(cmd *cobra.Command) {
108128 cmd .Flags ().IntVar (& o .HubHTTPSPort , "hub-https-port" , 8443 , "HTTPS port for kedge hub (change if 8443 is already in use)" )
109129 cmd .Flags ().IntVar (& o .HubHTTPPort , "hub-http-port" , 8080 , "HTTP port for kedge hub (change if 8080 is already in use)" )
110130 cmd .Flags ().StringVar (& o .ImagePullPolicy , "image-pull-policy" , "IfNotPresent" , "Image pull policy for the hub (use Never when the image is pre-loaded into kind)" )
131+ cmd .Flags ().BoolVar (& o .WithDex , "with-dex" , false , "Deploy Dex as OIDC identity provider into the hub kind cluster" )
132+ cmd .Flags ().IntVar (& o .DexHTTPPort , "dex-http-port" , 5556 , "Host port for the Dex NodePort mapping (default 5556)" )
111133}
112134
113135// Complete completes the options
@@ -180,6 +202,14 @@ func (o *DevOptions) Validate() error {
180202}
181203
182204func (o * DevOptions ) hubClusterConfig () string {
205+ dexMapping := ""
206+ if o .WithDex {
207+ dexMapping = fmt .Sprintf (` - containerPort: 31556
208+ hostPort: %d
209+ protocol: TCP
210+ listenAddress: "127.0.0.1"
211+ ` , o .DexHTTPPort )
212+ }
183213 return fmt .Sprintf (`apiVersion: kind.x-k8s.io/v1alpha4
184214kind: Cluster
185215networking:
@@ -196,7 +226,7 @@ nodes:
196226 hostPort: %d
197227 protocol: TCP
198228 listenAddress: "127.0.0.1"
199- ` , o .APIServerPort , o .HubHTTPPort , o .HubHTTPSPort )
229+ %s ` , o .APIServerPort , o .HubHTTPPort , o .HubHTTPSPort , dexMapping )
200230}
201231
202232var agentClusterConfig = `apiVersion: kind.x-k8s.io/v1alpha4
@@ -392,7 +422,20 @@ func (o *DevOptions) createCluster(ctx context.Context, clusterName, clusterConf
392422 if err != nil {
393423 return err
394424 }
395- if err := o .installHelmChart (ctx , restConfig ); err != nil {
425+
426+ // Deploy Dex FIRST so the hub can be installed once, with IDP settings
427+ // already wired in. Dex creates the namespace (CreateNamespace=true).
428+ if o .WithDex {
429+ _ , _ = fmt .Fprint (o .Streams .ErrOut , "Deploying Dex OIDC provider...\n " )
430+ if err := o .deployDex (ctx , restConfig , kubeconfigPath ); err != nil {
431+ return fmt .Errorf ("deploying dex: %w" , err )
432+ }
433+ _ , _ = fmt .Fprint (o .Streams .ErrOut , "Dex deployed\n " )
434+ }
435+
436+ // Single hub install: pass withIDP=o.WithDex so IDP settings are
437+ // included from the start when Dex is enabled.
438+ if err := o .installHelmChart (ctx , restConfig , o .WithDex ); err != nil {
396439 _ , _ = fmt .Fprint (o .Streams .ErrOut , "Failed to install Helm chart\n " )
397440 return err
398441 }
@@ -406,6 +449,108 @@ func loadRestConfigFromFile(kubeconfigPath string) (*rest.Config, error) {
406449 return clientcmd .BuildConfigFromFlags ("" , kubeconfigPath )
407450}
408451
452+ // ensureDexHelmRepo adds the dexidp helm repo if it isn't already present.
453+ // This is needed so that `helm install dexidp/dex` can resolve the chart.
454+ func ensureDexHelmRepo () error {
455+ addCmd := exec .Command ("helm" , "repo" , "add" , "dexidp" , "https://charts.dexidp.io" )
456+ // "already exists" is not an error; any other failure is.
457+ out , err := addCmd .CombinedOutput ()
458+ if err != nil && ! strings .Contains (string (out ), "already exists" ) {
459+ return fmt .Errorf ("adding dexidp helm repo: %w\n output: %s" , err , string (out ))
460+ }
461+ updateCmd := exec .Command ("helm" , "repo" , "update" , "dexidp" )
462+ if out , err := updateCmd .CombinedOutput (); err != nil {
463+ return fmt .Errorf ("updating dexidp helm repo: %w\n output: %s" , err , string (out ))
464+ }
465+ return nil
466+ }
467+
468+ // deployDex installs or upgrades the Dex Helm chart into the hub kind cluster
469+ // and blocks until the Dex pod is Running/Ready.
470+ func (o * DevOptions ) deployDex (ctx context.Context , restConfig * rest.Config , kubeconfigPath string ) error {
471+ actionConfig := new (action.Configuration )
472+ if err := actionConfig .Init (& restConfigGetter {config : restConfig }, "kedge-system" , "secret" ,
473+ func (format string , v ... any ) {}); err != nil {
474+ return fmt .Errorf ("initialising helm action config for dex: %w" , err )
475+ }
476+ regClient , err := registry .NewClient ()
477+ if err != nil {
478+ return fmt .Errorf ("creating helm registry client for dex: %w" , err )
479+ }
480+ actionConfig .RegistryClient = regClient
481+
482+ hubExternalURL := fmt .Sprintf ("https://kedge.localhost:%d" , o .HubHTTPSPort )
483+ redirectURI := hubExternalURL + "/auth/callback"
484+
485+ dexValues := map [string ]any {
486+ "image" : map [string ]any {"tag" : "v2.44.0" },
487+ "service" : map [string ]any {
488+ "type" : "NodePort" ,
489+ "ports" : map [string ]any {
490+ "http" : map [string ]any {"nodePort" : devDexNodePort },
491+ },
492+ },
493+ "config" : map [string ]any {
494+ "issuer" : devDexIssuerURL ,
495+ "storage" : map [string ]any {"type" : "memory" },
496+ "web" : map [string ]any {"http" : "0.0.0.0:5556" },
497+ "oauth2" : map [string ]any {"skipApprovalScreen" : true },
498+ "staticClients" : []map [string ]any {{
499+ "id" : devDexClientID ,
500+ "secret" : devDexClientSecret ,
501+ "name" : "Kedge Hub" ,
502+ "redirectURIs" : []string {redirectURI },
503+ }},
504+ "enablePasswordDB" : true ,
505+ "staticPasswords" : []map [string ]any {{
506+ "email" : "admin@test.kedge.local" ,
507+ "hash" : devDexUserHash ,
508+ "username" : "admin" ,
509+ "userID" : "test-user-id-01" ,
510+ }},
511+ },
512+ }
513+
514+ if err := ensureDexHelmRepo (); err != nil {
515+ return fmt .Errorf ("ensuring dex helm repo: %w" , err )
516+ }
517+
518+ tmp := action .NewInstall (actionConfig )
519+ tmp .Version = devDexChartVersion
520+ chartPath , err := tmp .LocateChart (devDexChartRef , cli .New ())
521+ if err != nil {
522+ return fmt .Errorf ("locating dex chart: %w" , err )
523+ }
524+ chartObj , err := loader .Load (chartPath )
525+ if err != nil {
526+ return fmt .Errorf ("loading dex chart: %w" , err )
527+ }
528+
529+ hist := action .NewHistory (actionConfig )
530+ hist .Max = 1
531+ if _ , err := hist .Run (devDexReleaseName ); err == nil {
532+ upg := action .NewUpgrade (actionConfig )
533+ upg .Namespace = "kedge-system"
534+ upg .Wait = true
535+ upg .Timeout = 3 * time .Minute
536+ if _ , err := upg .Run (devDexReleaseName , chartObj , dexValues ); err != nil {
537+ return fmt .Errorf ("upgrading dex chart: %w" , err )
538+ }
539+ } else {
540+ inst := action .NewInstall (actionConfig )
541+ inst .ReleaseName = devDexReleaseName
542+ inst .Namespace = "kedge-system"
543+ inst .CreateNamespace = true // Dex is deployed before the hub; create namespace here.
544+ inst .Wait = true
545+ inst .Timeout = 3 * time .Minute
546+ if _ , err := inst .Run (chartObj , dexValues ); err != nil {
547+ return fmt .Errorf ("installing dex chart: %w" , err )
548+ }
549+ }
550+
551+ return nil
552+ }
553+
409554func (o * DevOptions ) getClusterIPAddress (ctx context.Context , clusterName , networkName string ) (string , error ) {
410555 dockerClient , err := client .NewClientWithOpts (client .FromEnv , client .WithAPIVersionNegotiation ())
411556 if err != nil {
@@ -443,7 +588,11 @@ func (o *DevOptions) getClusterIPAddress(ctx context.Context, clusterName, netwo
443588 return "" , fmt .Errorf ("could not find IP address for cluster %s in network %s" , clusterName , networkName )
444589}
445590
446- func (o * DevOptions ) installHelmChart (_ context.Context , restConfig * rest.Config ) error {
591+ // installHelmChart installs or upgrades the kedge-hub Helm chart.
592+ // withIDP controls whether IDP/OIDC values are included; pass false for the
593+ // initial install (before Dex is deployed) and true for the upgrade after Dex
594+ // is up, so the hub never tries to contact a non-existent issuer at startup.
595+ func (o * DevOptions ) installHelmChart (_ context.Context , restConfig * rest.Config , withIDP bool ) error {
447596 actionConfig := new (action.Configuration )
448597
449598 if err := actionConfig .Init (& restConfigGetter {config : restConfig }, "kedge-system" , "secret" , func (format string , v ... any ) {}); err != nil {
@@ -457,6 +606,21 @@ func (o *DevOptions) installHelmChart(_ context.Context, restConfig *rest.Config
457606 }
458607 actionConfig .RegistryClient = registryClient
459608
609+ hubExternalURL := fmt .Sprintf ("https://kedge.localhost:%d" , o .HubHTTPSPort )
610+
611+ hubValues := map [string ]any {
612+ "hubExternalURL" : hubExternalURL ,
613+ "listenAddr" : fmt .Sprintf (":%d" , o .HubHTTPSPort ),
614+ "devMode" : true ,
615+ }
616+ // Static auth token is only used in token mode. In OIDC/IDP mode the hub
617+ // authenticates via Dex; mixing both would be confusing and unnecessary.
618+ if ! withIDP {
619+ hubValues ["staticAuthTokens" ] = []string {"dev-token" }
620+ }
621+ // IDP settings are passed via the top-level `idp` helm values (not under `hub`).
622+ // See deploy/charts/kedge-hub/templates/statefulset.yaml.
623+
460624 values := map [string ]any {
461625 "image" : map [string ]any {
462626 "hub" : map [string ]any {
@@ -465,14 +629,7 @@ func (o *DevOptions) installHelmChart(_ context.Context, restConfig *rest.Config
465629 "pullPolicy" : o .ImagePullPolicy ,
466630 },
467631 },
468- "hub" : map [string ]any {
469- "hubExternalURL" : fmt .Sprintf ("https://kedge.localhost:%d" , o .HubHTTPSPort ),
470- "listenAddr" : fmt .Sprintf (":%d" , o .HubHTTPSPort ),
471- "devMode" : true ,
472- "staticAuthTokens" : []string {
473- "dev-token" ,
474- },
475- },
632+ "hub" : hubValues ,
476633 "service" : map [string ]any {
477634 "type" : "NodePort" ,
478635 "hub" : map [string ]any {
@@ -481,6 +638,13 @@ func (o *DevOptions) installHelmChart(_ context.Context, restConfig *rest.Config
481638 },
482639 },
483640 }
641+ if withIDP && o .WithDex {
642+ values ["idp" ] = map [string ]any {
643+ "issuerURL" : devDexIssuerURL ,
644+ "clientID" : devDexClientID ,
645+ "clientSecret" : devDexClientSecret ,
646+ }
647+ }
484648
485649 var chartObj * chart.Chart
486650 var err error
0 commit comments