Add initial admin

[ZITADOPER-1]
This commit is contained in:
Haim Kortovich
2024-05-15 19:49:16 -05:00
parent 3795cbdca4
commit 624b99d371
14 changed files with 197 additions and 24 deletions

View File

@@ -39,7 +39,7 @@ spec:
- ACCESS_TOKEN_TYPE_BEARER
- ACCESS_TOKEN_TYPE_JWT
type: string
zitadelClusterRef:
organizationRef:
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
Important: Run "make" to regenerate code after modifying this file'
properties:
@@ -79,7 +79,7 @@ spec:
x-kubernetes-map-type: atomic
required:
- accessTokenType
- zitadelClusterRef
- organizationRef
type: object
status:
description: MachineUserStatus defines the observed state of MachineUser

View File

@@ -77,10 +77,13 @@ spec:
externalSecure:
default: true
type: boolean
host:
firstOrgName:
default: DEFAULT
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
Important: Run "make" to regenerate code after modifying this file'
type: string
host:
type: string
image:
properties:
name:
@@ -165,6 +168,7 @@ spec:
- crdbClusterRef
- externalPort
- externalSecure
- firstOrgName
- host
- image
- purpose
@@ -247,12 +251,16 @@ spec:
defaultInstanceId:
default: ""
type: string
initialAdminId:
default: ""
type: string
replicas:
default: 3
format: int32
type: integer
required:
- defaultInstanceId
- initialAdminId
type: object
type: object
served: true

View File

@@ -18,6 +18,7 @@ package v1alpha1
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -82,10 +83,17 @@ func (d *APIApp) ZitadelClusterRef(ctx context.Context, refresolver *RefResolver
if err != nil {
return nil, err
}
if project.Status.ProjectId == "" {
return nil, fmt.Errorf("Project has not been created yet...")
}
org, err := refresolver.OrganizationRef(ctx, &project.Spec.OrganizationRef, d.Namespace)
if err != nil {
return nil, err
}
if org.Status.OrgId == "" {
return nil, fmt.Errorf("Organization has not been created yet...")
}
ref, err := org.ZitadelClusterRef(ctx, refresolver)
if err != nil {
return nil, err

View File

@@ -18,6 +18,8 @@ package v1alpha1
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -31,7 +33,7 @@ type MachineUserSpec struct {
// Important: Run "make" to regenerate code after modifying this file
// +kubebuilder:validation:Required
// +operator-sdk:csv:customresourcedefinitions:type=spec
ZitadelClusterRef ZitadelClusterRef `json:"zitadelClusterRef" webhook:"inmutable"`
OrganizationRef OrganizationRef `json:"organizationRef" webhook:"inmutable"`
// +kubebuilder:validation:Enum=ACCESS_TOKEN_TYPE_BEARER;ACCESS_TOKEN_TYPE_JWT
AccessTokenType string `json:"accessTokenType"`
}
@@ -77,7 +79,19 @@ func (d *MachineUser) IsReady() bool {
}
func (d *MachineUser) ZitadelClusterRef(ctx context.Context, refresolver *RefResolver) (*ZitadelClusterRef, error) {
return &d.Spec.ZitadelClusterRef, nil
org, err := refresolver.OrganizationRef(ctx, &d.Spec.OrganizationRef, d.Namespace)
if err != nil {
return nil, err
}
if org.Status.OrgId == "" {
return nil, fmt.Errorf("Organization has not been created yet...")
}
ref, err := org.ZitadelClusterRef(ctx, refresolver)
if err != nil {
return nil, err
}
return ref, nil
}
//+kubebuilder:object:root=true

View File

@@ -18,6 +18,8 @@ package v1alpha1
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -103,10 +105,18 @@ func (d *OIDCApp) ZitadelClusterRef(ctx context.Context, refresolver *RefResolve
if err != nil {
return nil, err
}
if project.Status.ProjectId == "" {
return nil, fmt.Errorf("Project has not been created yet...")
}
org, err := refresolver.OrganizationRef(ctx, &project.Spec.OrganizationRef, d.Namespace)
if err != nil {
return nil, err
}
if org.Status.OrgId == "" {
return nil, fmt.Errorf("Organization has not been created yet...")
}
ref, err := org.ZitadelClusterRef(ctx, refresolver)
if err != nil {
return nil, err

View File

@@ -18,6 +18,7 @@ package v1alpha1
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -94,6 +95,10 @@ func (d *Project) ZitadelClusterRef(ctx context.Context, refresolver *RefResolve
if err != nil {
return nil, err
}
if org.Status.OrgId == "" {
return nil, fmt.Errorf("Organization has not been created yet...")
}
ref, err := org.ZitadelClusterRef(ctx, refresolver)
if err != nil {
return nil, err

View File

@@ -34,6 +34,8 @@ type Image struct {
type ZitadelClusterSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// +kubebuilder:default="DEFAULT"
FirstOrgName string `json:"firstOrgName"`
Host string `json:"host"`
// +kubebuilder:default=443
ExternalPort int64 `json:"externalPort"`
@@ -67,6 +69,8 @@ type ZitadelClusterStatus struct {
Replicas int32 `json:"replicas,omitempty"`
// +kubebuilder:default=""
DefaultInstanceId string `json:"defaultInstanceId"`
// +kubebuilder:default=""
InitialAdminId string `json:"initialAdminId"`
}
// SetCondition sets a status condition

View File

@@ -216,7 +216,7 @@ func (in *MachineUserList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MachineUserSpec) DeepCopyInto(out *MachineUserSpec) {
*out = *in
out.ZitadelClusterRef = in.ZitadelClusterRef
out.OrganizationRef = in.OrganizationRef
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineUserSpec.

View File

@@ -40,7 +40,7 @@ spec:
- ACCESS_TOKEN_TYPE_BEARER
- ACCESS_TOKEN_TYPE_JWT
type: string
zitadelClusterRef:
organizationRef:
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
Important: Run "make" to regenerate code after modifying this file'
properties:
@@ -80,7 +80,7 @@ spec:
x-kubernetes-map-type: atomic
required:
- accessTokenType
- zitadelClusterRef
- organizationRef
type: object
status:
description: MachineUserStatus defines the observed state of MachineUser

View File

@@ -78,10 +78,13 @@ spec:
externalSecure:
default: true
type: boolean
host:
firstOrgName:
default: DEFAULT
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
Important: Run "make" to regenerate code after modifying this file'
type: string
host:
type: string
image:
properties:
name:
@@ -166,6 +169,7 @@ spec:
- crdbClusterRef
- externalPort
- externalSecure
- firstOrgName
- host
- image
- purpose
@@ -248,12 +252,16 @@ spec:
defaultInstanceId:
default: ""
type: string
initialAdminId:
default: ""
type: string
replicas:
default: 3
format: int32
type: integer
required:
- defaultInstanceId
- initialAdminId
type: object
type: object
served: true

View File

@@ -11,6 +11,7 @@ import (
condition "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/condition"
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/controller/zitadel"
"github.com/zitadel/zitadel-go/v2/pkg/client/management"
"github.com/zitadel/zitadel-go/v2/pkg/client/middleware"
"github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/authn"
pb "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/management"
user "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/user"
@@ -85,12 +86,27 @@ func newWrappedMachineUserReconciler(client client.Client, refResolver *zitadelv
func (wr *wrappedMachineUserReconciler) Reconcile(ctx context.Context, ztdClient *management.Client) error {
// TODO: update machine user
zitadel, err := wr.refResolver.ZitadelCluster(ctx, &wr.MachineUser.Spec.ZitadelClusterRef, wr.MachineUser.Namespace)
org, err := wr.refResolver.OrganizationRef(ctx, &wr.MachineUser.Spec.OrganizationRef, wr.MachineUser.Namespace)
if err != nil {
return err
}
if wr.MachineUser.Status.UserId != "" {
_, err = ztdClient.UpdateMachine(middleware.SetOrgID(ctx, org.Status.OrgId),
&pb.UpdateMachineRequest{
UserId: wr.MachineUser.Status.UserId,
Name: wr.MachineUser.Name,
Description: wr.MachineUser.Name,
AccessTokenType: user.AccessTokenType(user.AccessTokenType_value[wr.MachineUser.Spec.AccessTokenType]),
})
if err != nil {
if !strings.Contains(err.Error(), "No changes") {
return fmt.Errorf("Error updating OIDCApp: %v", err)
}
}
return nil
}
resp, err := ztdClient.AddMachineUser(ctx,
resp, err := ztdClient.AddMachineUser(middleware.SetOrgID(ctx, org.Status.OrgId),
&pb.AddMachineUserRequest{
Name: wr.MachineUser.Name,
UserName: wr.MachineUser.Name,
@@ -121,17 +137,18 @@ func (wr *wrappedMachineUserReconciler) Reconcile(ctx context.Context, ztdClient
return fmt.Errorf("Error Adding MachineKey: %v", err)
}
key := types.NamespacedName{
Name: wr.MachineUser.Name + "-key-secret",
Name: wr.MachineUser.Name + "-machinekey-secret",
Namespace: wr.MachineUser.Namespace,
}
secret, err := wr.Builder.BuildSecret(builder.SecretOpts{
Zitadel: zitadel,
secret, err := wr.Builder.BuildSecret(
builder.SecretOpts{
Key: key,
Immutable: true,
Data: map[string][]byte{
"key": respKey.KeyDetails,
"key.json": respKey.KeyDetails,
},
}, wr.MachineUser)
if err != nil {
return fmt.Errorf("error building Secret: %v", err)
}

View File

@@ -122,6 +122,7 @@ func (wr *wrappedOIDCAppReconciler) Reconcile(ctx context.Context, ztdClient *ma
ProjectId: project.Status.ProjectId,
AppId: string(wr.OIDCApp.Status.AppId),
})
// TODO: fix flow
if err != nil {
return fmt.Errorf("Error getting OIDCApp: %v", err)
}

View File

@@ -91,7 +91,10 @@ func newWrappedOrganizationReconciler(client client.Client, refResolver *zitadel
}
func (wr *wrappedOrganizationReconciler) Reconcile(ctx context.Context, ztdClient *management.Client) error {
resp, err := ztdClient.AddOrg(ctx, &pb.AddOrgRequest{Name: wr.organization.Name})
// TODO: check if org exists first
resp, err := ztdClient.AddOrg(ctx, &pb.AddOrgRequest{
Name: wr.organization.Name,
})
if err != nil {
if strings.Contains(err.Error(), "AlreadyExists") {
return nil

View File

@@ -27,6 +27,7 @@ import (
"time"
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/admin"
builder "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/builder"
condition "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/condition"
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/configuration"
@@ -39,9 +40,12 @@ import (
systemapiaccount "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/systemapi"
zitadelClient "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/zitadel"
"github.com/hashicorp/go-multierror"
"github.com/zitadel/zitadel-go/v2/pkg/client/middleware"
"github.com/zitadel/zitadel-go/v2/pkg/client/system"
authn "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/authn"
"github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/management"
pb "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/system"
"github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/user"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
@@ -148,6 +152,14 @@ func (r *ZitadelClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque
Name: "DefaultInstance",
Reconcile: r.reconcileDefaultInstance,
},
{
Name: "InitialAdminSecret",
Reconcile: r.reconcileInitialAdminPassword,
},
{
Name: "InitialAdmin",
Reconcile: r.reconcileInitialHumanUser,
},
}
for _, p := range phases {
@@ -390,8 +402,8 @@ func (r *ZitadelClusterReconciler) reconcileDefaultInstance(ctx context.Context,
if strings.Contains(err.Error(), "Instance not found") {
// if Instance doesn't exist, then create and assign secrets
resp, err := ztdClient.CreateInstance(ctx, &pb.CreateInstanceRequest{
InstanceName: "DEFAULT",
FirstOrgName: "DEFAULT",
InstanceName: zitadel.Spec.FirstOrgName,
FirstOrgName: zitadel.Spec.FirstOrgName,
CustomDomain: zitadel.Spec.Host,
Owner: &pb.CreateInstanceRequest_Machine_{Machine: &pb.CreateInstanceRequest_Machine{
Name: "k8s-operator",
@@ -436,6 +448,89 @@ func (r *ZitadelClusterReconciler) reconcileDefaultInstance(ctx context.Context,
return ctrl.Result{}, nil
}
func (r *ZitadelClusterReconciler) reconcileInitialAdminPassword(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
secretName := admin.AdminPasswordSecretName(zitadel)
key := types.NamespacedName{
Name: secretName,
Namespace: zitadel.Namespace,
}
_, err := r.SecretReconciler.ReconcileRandomPassword(ctx, key, systemapiaccount.Key, zitadel)
if err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *ZitadelClusterReconciler) reconcileInitialHumanUser(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
managementClient, err := zitadelClient.NewClient(ctx, zitadel, *r.RefResolver)
if err != nil {
return ctrl.Result{}, err
}
defer managementClient.Connection.Close()
secretName := admin.AdminPasswordSecretName(zitadel)
key := types.NamespacedName{
Name: secretName,
Namespace: zitadel.Namespace,
}
password, err := r.SecretReconciler.ReconcileRandomPassword(ctx, key, admin.Key, zitadel)
if err != nil {
return ctrl.Result{}, err
}
org, err := managementClient.GetMyOrg(ctx, &management.GetMyOrgRequest{})
if err != nil {
return ctrl.Result{}, fmt.Errorf("Error getting org: %v", err)
}
users, err := managementClient.ListUsers(middleware.SetOrgID(ctx, org.Org.Id), &management.ListUsersRequest{})
if err != nil {
return ctrl.Result{}, fmt.Errorf("Error getting users: %v", err)
}
userid := zitadel.Status.InitialAdminId
for _, u := range users.Result {
if admin.AccountName == u.UserName {
userid = u.Id
}
}
if userid == "" {
resp, err := managementClient.AddHumanUser(middleware.SetOrgID(ctx, org.Org.Id), &management.AddHumanUserRequest{
UserName: admin.AccountName,
Profile: &management.AddHumanUserRequest_Profile{
FirstName: admin.AccountName,
LastName: admin.AccountName,
NickName: admin.AccountName,
DisplayName: admin.AccountName,
Gender: user.Gender_GENDER_DIVERSE,
PreferredLanguage: "en",
},
InitialPassword: password,
Email: &management.AddHumanUserRequest_Email{
Email: "test@test.com",
IsEmailVerified: true,
},
})
userid = resp.UserId
if err != nil {
return ctrl.Result{}, fmt.Errorf("Error adding human user: %v", err)
}
{
if _, err = managementClient.AddOrgMember(middleware.SetOrgID(ctx, org.Org.Id), &management.AddOrgMemberRequest{
UserId: userid,
Roles: []string{
"IAM_OWNER",
},
}); err != nil {
return ctrl.Result{}, fmt.Errorf("Error adding org member: %v", err)
}
}
}
patch := client.MergeFrom(zitadel.DeepCopy())
zitadel.Status.InitialAdminId = userid
return ctrl.Result{}, r.Status().Patch(ctx, zitadel, patch)
}
func GetIssuer(zitadel *zitadelv1alpha1.ZitadelCluster) string {
scheme := "http"
if zitadel.Spec.ExternalSecure {