package main import ( "context" "database/sql" "encoding/json" "flag" "fmt" "io/ioutil" "log" "os" "path" "time" kclient "github.com/ory/kratos-client-go" "github.com/paralus/paralus/internal/dao" "github.com/paralus/paralus/internal/models" providers "github.com/paralus/paralus/internal/provider/kratos" "github.com/paralus/paralus/pkg/audit" "github.com/paralus/paralus/pkg/common" "github.com/paralus/paralus/pkg/enforcer" "github.com/paralus/paralus/pkg/service" commonv3 "github.com/paralus/paralus/proto/types/commonpb/v3" rolev3 "github.com/paralus/paralus/proto/types/rolepb/v3" systemv3 "github.com/paralus/paralus/proto/types/systempb/v3" userv3 "github.com/paralus/paralus/proto/types/userpb/v3" "github.com/spf13/viper" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" "github.com/uptrace/bun/driver/pgdriver" "github.com/uptrace/bun/extra/bundebug" "gorm.io/driver/postgres" "gorm.io/gorm" ) // This script will be run in an init container after we crate all the // permissions. It will take care of the initialization, namely: // - creating partner // - creating org // - creating roles in org // // We make use of service instead of just insserting to db as that way // all the dependent items will be taken care of automatically. // Inorder to reset everything, we can do // truncate table authsrv_partner cascade; // truncate table casbin_rule; const ( dbDSNEnv = "DSN" dbAddrEnv = "DB_ADDR" dbNameEnv = "DB_NAME" dbUserEnv = "DB_USER" dbPasswordEnv = "DB_PASSWORD" kratosAddrEnv = "KRATOS_ADDR" auditFileEnv = "AUDIT_LOG_FILE" ) func addResourcePermissions(db *bun.DB, basePath string) error { var items []models.ResourcePermission files, err := ioutil.ReadDir(basePath) if err != nil { log.Fatal(err) } for _, file := range files { if !file.IsDir() { // probably not, but just in case content, err := ioutil.ReadFile(path.Join(basePath, file.Name())) if err != nil { log.Fatal(err) } // It has ResourceRefId, but that does not seem to be used in the old implementation // Also, why do we need two items? var data models.ResourcePermission err = json.Unmarshal(content, &data) if err != nil { log.Fatal(err) } items = append(items, data) } } fmt.Println("Adding", len(items), "resource permissions") _, err = dao.Create(context.Background(), db, &items) return err } func main() { partner := flag.String("partner", "DefaultPartner", "Name of partner") partnerDesc := flag.String("partner-desc", "", "Description of partner") partnerHost := flag.String("partner-host", "", "Host of partner") org := flag.String("org", "DefaultOrg", "Name of org") orgDesc := flag.String("org-desc", "", "Description of org") oae := flag.String("admin-email", "", "Email of org admin") oafn := flag.String("admin-first-name", "", "First name of org admin") oaln := flag.String("admin-last-name", "", "Last name of org admin") debug := flag.Bool("debug", false, "Enable verbose mode") flag.Parse() if *partner == "" || *org == "" || *oae == "" || *oafn == "" || *oaln == "" || *partnerHost == "" { fmt.Println("Usage: initialize") flag.PrintDefaults() os.Exit(1) } viper.SetDefault(dbAddrEnv, "localhost:5432") viper.SetDefault(dbNameEnv, "admindb") viper.SetDefault(dbUserEnv, "admindbuser") viper.SetDefault(dbPasswordEnv, "admindbpassword") viper.SetDefault(kratosAddrEnv, "http://localhost:4433") viper.SetDefault(auditFileEnv, "audit.log") viper.BindEnv(auditFileEnv) viper.BindEnv(dbDSNEnv) viper.BindEnv(dbAddrEnv) viper.BindEnv(dbNameEnv) viper.BindEnv(dbUserEnv) viper.BindEnv(dbPasswordEnv) viper.BindEnv(kratosAddrEnv) dbDSN := viper.GetString(dbDSNEnv) dbAddr := viper.GetString(dbAddrEnv) dbName := viper.GetString(dbNameEnv) dbUser := viper.GetString(dbUserEnv) dbPassword := viper.GetString(dbPasswordEnv) kratosAddr := viper.GetString(kratosAddrEnv) auditFile := viper.GetString(auditFileEnv) content, err := ioutil.ReadFile(path.Join("scripts", "initialize", "roles", "ztka", "roles.json")) if err != nil { log.Fatal("unable to read roles file: ", err) } var data map[string]map[string][]string err = json.Unmarshal(content, &data) if err != nil { log.Fatal("unable to parse roles file", err) } content, err = ioutil.ReadFile(path.Join("scripts", "initialize", "roles", "desc.json")) if err != nil { log.Fatal("unable to read role descriptions file: ", err) } var roleDesc map[string]string err = json.Unmarshal(content, &roleDesc) if err != nil { log.Fatal("unable to parse role descriptions file", err) } if dbDSN == "" { dbDSN = fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", dbUser, dbPassword, dbAddr, dbName) } sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dbDSN))) db := bun.NewDB(sqldb, pgdialect.New()) if *debug { db.AddQueryHook(bundebug.NewQueryHook( bundebug.WithVerbose(true), bundebug.FromEnv("BUNDEBUG"), )) } kratosConfig := kclient.NewConfiguration() kratosConfig.Servers[0].URL = kratosAddr kc := kclient.NewAPIClient(kratosConfig) ao := audit.AuditOptions{ LogPath: auditFile, MaxSizeMB: 1, MaxBackups: 10, // Should we let sidecar do rotation? MaxAgeDays: 10, // Make these configurable via env } auditLogger := audit.GetAuditLogger(&ao) // authz services gormDb, err := gorm.Open(postgres.Open(dbDSN), &gorm.Config{}) if err != nil { log.Fatal("unable to create db connection", "error", err) } enforcer, err := enforcer.NewCasbinEnforcer(gormDb).Init() if err != nil { log.Fatal("unable to init enforcer", "error", err) } as := service.NewAuthzService(db, enforcer) ps := service.NewPartnerService(db, auditLogger) os := service.NewOrganizationService(db, auditLogger) rs := service.NewRoleService(db, as, auditLogger) gs := service.NewGroupService(db, as, auditLogger) us := service.NewUserService(providers.NewKratosAuthProvider(kc), db, as, nil, common.CliConfigDownloadData{}, auditLogger, true) prs := service.NewProjectService(db, as, auditLogger, true) //check if there are role permissions already present existingPermissions := &[]models.ResourceRolePermission{} _, err = dao.ListAll(context.Background(), db, existingPermissions) if err != nil { log.Fatal("Error verifying existing role permissions ", err) } if len(*existingPermissions) > 0 { fmt.Println("resource permissions already exists! cannot invoke initialize again") return } //add resource permissions err = addResourcePermissions(db, path.Join("scripts", "initialize", "permissions", "base")) if err != nil { log.Fatal("Error running from base directory ", err) } err = addResourcePermissions(db, path.Join("scripts", "initialize", "permissions", "ztka")) if err != nil { log.Fatal("Error running from ztka directory ", err) } // Create partner _, err = ps.Create(context.Background(), &systemv3.Partner{ Metadata: &commonv3.Metadata{Name: *partner, Description: *partnerDesc}, Spec: &systemv3.PartnerSpec{Host: *partnerHost}, }) if err != nil { log.Fatal("unable to create partner", err) } _, err = os.Create(context.Background(), &systemv3.Organization{ Metadata: &commonv3.Metadata{Name: *org, Partner: *partner, Description: *orgDesc}, Spec: &systemv3.OrganizationSpec{Active: true}, }) if err != nil { log.Fatal("unable to create organization", err) } // this is used to figure out if the request originated internally so as to not override `builtin` internalCtx := context.WithValue(context.Background(), common.SessionInternalKey, true) for scope := range data { for name := range data[scope] { perms := data[scope][name] fmt.Println(scope, name, len(perms)) _, err := rs.Create(internalCtx, &rolev3.Role{ Metadata: &commonv3.Metadata{Name: name, Partner: *partner, Organization: *org, Description: roleDesc[name]}, Spec: &rolev3.RoleSpec{IsGlobal: true, Scope: scope, Rolepermissions: perms, Builtin: true}, }) if err != nil { log.Fatalf("unable to create rolepermission %s %s: %s", scope, name, err) } } } //default "All Local Users" group should be created localUsersGrp, err := gs.Create(context.Background(), &userv3.Group{ Metadata: &commonv3.Metadata{ Name: "All Local Users", Partner: *partner, Organization: *org, Description: "Default group for all local users", }, Spec: &userv3.GroupSpec{ Type: "DEFAULT_USERS", }, }) if err != nil { log.Fatal("unable to create default group", err) } //default "Organization Admins" group should be created admingrp, err := gs.Create(context.Background(), &userv3.Group{ Metadata: &commonv3.Metadata{ Name: "Organization Admins", Partner: *partner, Organization: *org, Description: "Default organization admin group", }, Spec: &userv3.GroupSpec{ Type: "DEFAULT_ADMINS", ProjectNamespaceRoles: []*userv3.ProjectNamespaceRole{ { Role: "ADMIN", }, }, }, }) if err != nil { log.Fatal("unable to create default group", err) } //default project with name "default" should be created with default flag true prs.Create(context.Background(), &systemv3.Project{ Metadata: &commonv3.Metadata{ Name: "default", Description: "Default project", Partner: *partner, Organization: *org, }, Spec: &systemv3.ProjectSpec{ Default: true, }, }) retry: numOfRetries := 0 // should we directly interact with kratos and create a user with a password? orgA, err := us.Create(context.Background(), &userv3.User{ Metadata: &commonv3.Metadata{Name: *oae, Partner: *partner, Organization: *org}, Spec: &userv3.UserSpec{ FirstName: *oafn, LastName: *oaln, Groups: []string{admingrp.Metadata.Name, localUsersGrp.Metadata.Name}, ProjectNamespaceRoles: []*userv3.ProjectNamespaceRole{{Role: "ADMIN", Group: &admingrp.Metadata.Name}}}, }) if err != nil { fmt.Println("err:", err) numOfRetries = +1 if numOfRetries > 20 { log.Fatal("unable to bind user to role", err) } fmt.Println("retrying in 10s, waiting for kratos to be up ... ") time.Sleep(10 * time.Second) goto retry } fmt.Println("Org Admin signup URL: ", *orgA.Spec.RecoveryUrl) }