Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
SHELL := bash
NAME := idm
TAGS := disable_crypt
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
include ../../.bingo/Variables.mk
endif
include ../../.make/default.mk
include ../../.make/go.mk
include ../../.make/release.mk
include ../../.make/docs.mk
+21
View File
@@ -0,0 +1,21 @@
# IDM
The IDM service provides a minimal LDAP Service, based on [Libregraph idm](https://github.com/libregraph/idm), for КуСфера. It is started as part of the default configuration and serves as a central place for storing user and group information.
It is mainly targeted at small КуСфера installations. For larger setups it is recommended to replace IDM with a “real” LDAP server or to switch to an external identity management solution.
IDM listens on port 9235 by default. In the default configuration it only accepts TLS-protected connections (LDAPS). The BaseDN of the LDAP tree is `o=libregraph-idm`. IDM gives LDAP write permissions to a single user (DN: `uid=libregraph,ou=sysusers,o=libregraph-idm`). Any other authenticated user has read-only access. IDM stores its data in a boltdb file `idm/idm.boltdb` inside the КуСфера base data directory.
The internal LDAP certificate and key are stored as `ldap.crt` and `ldap.key` in the IDM data directory. By default, these certificates expire after 12 months. When the certificate has expired, IDM can no longer establish valid TLS connections and requests that depend on LDAP may fail with `500 Internal Server Error`.
To renew the internal LDAP certificate, stop or restart the КуСфера container after deleting the expired certificate and key:
```bash
cd .qsfera/idm
rm ldap.crt ldap.key
docker compose restart
```
The certificate and key are automatically regenerated when the container starts again. For more details, see [Internal LibreIDM cert expires](https://docs.qsfera.eu/docs/admin/resources/common-issues/#internal-libreidm-cert-expires).
Note: IDM is limited in its functionality. It only supports a subset of the LDAP operations (namely `BIND`, `SEARCH`, `ADD`, `MODIFY`, `DELETE`). Also, IDM currently does not do any schema verification (like. structural vs. auxiliary object classes, require and option attributes, syntax checks, …). Therefore it is not meant as a general purpose LDAP server.
+15
View File
@@ -0,0 +1,15 @@
package idm
import (
_ "embed"
)
// BaseLDIF is a template for base LDAP entries
//
//go:embed ldif/base.ldif.tmpl
var BaseLDIF string
// DemoUsersLDIF is a template for demo users
//
//go:embed ldif/demousers.ldif.tmpl
var DemoUsersLDIF string
+45
View File
@@ -0,0 +1,45 @@
dn: o=libregraph-idm
o: libregraph-idm
objectClass: organization
dn: ou=users,o=libregraph-idm
objectClass: organizationalUnit
ou: users
dn: ou=sysusers,o=libregraph-idm
objectClass: organizationalUnit
ou: users
dn: ou=groups,o=libregraph-idm
objectClass: organizationalUnit
ou: groups
{{ range . -}}
{{ if eq .Name "admin" -}}
dn: uid=admin,ou=users,o=libregraph-idm
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: qsferaUser
objectClass: qsferaObject
objectClass: person
objectClass: top
uid: admin
givenName: Admin
sn: Admin
cn: admin
displayName: Admin
description: An admin for this КуСфера instance.
mail: admin@example.org
qsferaUUID: {{ .ID }}
qsferaExternalIdentity: $ {{ .Issuer }} $ {{ .ID }}
{{ else -}}
dn: uid={{ .Name }},ou=sysusers,o=libregraph-idm
objectClass: account
objectClass: simpleSecurityObject
uid: {{ .Name }}
{{ end -}}
userPassword:: {{ .Password }}
{{ end -}}
@@ -0,0 +1,183 @@
dn: uid=alan,ou=users,o=libregraph-idm
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: qsferaUser
objectClass: qsferaObject
objectClass: person
objectClass: top
uid: alan
givenName: Alan
sn: Turing
cn: alan
displayName: Alan Turing
description: An English mathematician, computer scientist, logician, cryptanalyst, philosopher and theoretical biologist. He was highly influential in the development of theoretical computer science, providing a formalisation of the concepts of algorithm and computation with the Turing machine.
mail: alan@example.org
qsferaUserEnabled: TRUE
qsferaUUID: b1f74ec4-dd7e-11ef-a543-03775734d0f7
qsferaExternalIdentity: $ {{.}} $ b1f74ec4-dd7e-11ef-a543-03775734d0f7
userPassword:: e0FSR09OMn0kYXJnb24yaWQkdj0xOSRtPTY1NTM2LHQ9MSxwPTE2JGg1NUxqckhWVjdEdXVzTkxjbXRoa0EkMzZ3aGZSMjdyTDFOYXQxa0xTajdrVGFubTBnb3VKRGZ0ck9DTStuRHo5cw==
dn: uid=lynn,ou=users,o=libregraph-idm
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: qsferaUser
objectClass: qsferaObject
objectClass: person
objectClass: top
uid: lynn
givenName: Lynn
sn: Conway
cn: lynn
displayName: Lynn Conway
description: An American computer scientist, electrical engineer, and transgender activist.
mail: lynn@example.org
qsferaUserEnabled: TRUE
qsferaUUID: 60708dda-e897-11ef-919f-bbb7437d6ec2
qsferaExternalIdentity: $ {{.}} $ 60708dda-e897-11ef-919f-bbb7437d6ec2
userPassword:: e0FSR09OMn0kYXJnb24yaWQkdj0xOSRtPTY1NTM2LHQ9MSxwPTE2JGg1NUxqckhWVjdEdXVzTkxjbXRoa0EkMzZ3aGZSMjdyTDFOYXQxa0xTajdrVGFubTBnb3VKRGZ0ck9DTStuRHo5cw==
dn: uid=mary,ou=users,o=libregraph-idm
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: qsferaUser
objectClass: qsferaObject
objectClass: person
objectClass: top
uid: mary
givenName: Mary
sn: Kenneth Keller
cn: mary
displayName: Mary Kenneth Keller
description: Mary Kenneth Keller of the Sisters of Charity of the Blessed Virgin Mary was a pioneer in computer science.
mail: mary@example.org
qsferaUserEnabled: TRUE
qsferaUUID: 056fc874-dd7f-11ef-ba84-af6fca4b7289
qsferaExternalIdentity: $ {{.}} $ 056fc874-dd7f-11ef-ba84-af6fca4b7289
userPassword:: e0FSR09OMn0kYXJnb24yaWQkdj0xOSRtPTY1NTM2LHQ9MSxwPTE2JGg1NUxqckhWVjdEdXVzTkxjbXRoa0EkMzZ3aGZSMjdyTDFOYXQxa0xTajdrVGFubTBnb3VKRGZ0ck9DTStuRHo5cw==
dn: uid=margaret,ou=users,o=libregraph-idm
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: qsferaUser
objectClass: qsferaObject
objectClass: person
objectClass: top
uid: margaret
givenName: Margaret
sn: Hamilton
cn: margaret
displayName: Margaret Hamilton
description: A director of the Software Engineering Division of the MIT Instrumentation Laboratory, which developed on-board flight software for NASA's Apollo program.
mail: margaret@example.org
qsferaUserEnabled: TRUE
qsferaUUID: 801abee4-dd7f-11ef-a324-83f55a754b62
qsferaExternalIdentity: $ {{.}} $ 801abee4-dd7f-11ef-a324-83f55a754b62
userPassword:: e0FSR09OMn0kYXJnb24yaWQkdj0xOSRtPTY1NTM2LHQ9MSxwPTE2JGg1NUxqckhWVjdEdXVzTkxjbXRoa0EkMzZ3aGZSMjdyTDFOYXQxa0xTajdrVGFubTBnb3VKRGZ0ck9DTStuRHo5cw==
dn: uid=dennis,ou=users,o=libregraph-idm
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: qsferaUser
objectClass: qsferaObject
objectClass: person
objectClass: top
uid: dennis
givenName: Dennis
sn: Ritchie
cn: dennis
displayName: Dennis Ritchie
description: American computer scientist. He created the C programming language and the Unix operating system and B language with long-time colleague Ken Thompson.
mail: dennis@example.org
qsferaUserEnabled: TRUE
qsferaUUID: cd88bf9a-dd7f-11ef-a609-7f78deb2345f
qsferaExternalIdentity: $ {{.}} $ cd88bf9a-dd7f-11ef-a609-7f78deb2345f
userPassword:: e0FSR09OMn0kYXJnb24yaWQkdj0xOSRtPTY1NTM2LHQ9MSxwPTE2JGg1NUxqckhWVjdEdXVzTkxjbXRoa0EkMzZ3aGZSMjdyTDFOYXQxa0xTajdrVGFubTBnb3VKRGZ0ck9DTStuRHo5cw==
dn: cn=users,ou=groups,o=libregraph-idm
objectClass: groupOfNames
objectClass: qsferaObject
objectClass: top
cn: users
description: Users
qsferaUUID: 509a9dcd-bb37-4f4f-a01a-19dca27d9cfa
member: uid=alan,ou=users,o=libregraph-idm
member: uid=mary,ou=users,o=libregraph-idm
member: uid=margaret,ou=users,o=libregraph-idm
member: uid=dennis,ou=users,o=libregraph-idm
member: uid=lynn,ou=users,o=libregraph-idm
member: uid=admin,ou=users,o=libregraph-idm
dn: cn=chess-lovers,ou=groups,o=libregraph-idm
objectClass: groupOfNames
objectClass: qsferaObject
objectClass: top
cn: chess-lovers
description: Chess lovers
qsferaUUID: 9d31ec04-dd80-11ef-ac47-a38ba68cc36d
member: uid=alan,ou=users,o=libregraph-idm
dn: cn=machine-lovers,ou=groups,o=libregraph-idm
objectClass: groupOfNames
objectClass: qsferaObject
objectClass: top
cn: machine-lovers
description: Machine Lovers
qsferaUUID: d901562a-dd80-11ef-a510-fba1ed43fb21
member: uid=alan,ou=users,o=libregraph-idm
dn: cn=bible-readers,ou=groups,o=libregraph-idm
objectClass: groupOfNames
objectClass: qsferaObject
objectClass: top
cn: bible-readers
description: Bible readers
qsferaUUID: 2fc6ba22-dd81-11ef-89e6-e3eff494a998
member: uid=mary,ou=users,o=libregraph-idm
dn: cn=apollos,ou=groups,o=libregraph-idm
objectClass: groupOfNames
objectClass: qsferaObject
objectClass: top
cn: apollos
description: Contributors to the Appollo mission
qsferaUUID: 6f9bab36-dd94-11ef-a252-dbbdd20299dd
member: uid=margaret,ou=users,o=libregraph-idm
dn: cn=unix-lovers,ou=groups,o=libregraph-idm
objectClass: groupOfNames
objectClass: qsferaObject
objectClass: top
cn: unix-lovers
description: Unix lovers
qsferaUUID: 75bc3882-dd94-11ef-ad60-335f3df6cef3
member: uid=dennis,ou=users,o=libregraph-idm
dn: cn=basic-haters,ou=groups,o=libregraph-idm
objectClass: groupOfNames
objectClass: qsferaObject
objectClass: top
cn: basic-haters
description: Haters of the Basic programming language
qsferaUUID: a4eb2c12-dd94-11ef-9ebe-eb96f938d517
member: uid=dennis,ou=users,o=libregraph-idm
dn: cn=vlsi-lovers,ou=groups,o=libregraph-idm
objectClass: groupOfNames
objectClass: qsferaObject
objectClass: top
cn: vlsi-lovers
description: Lovers of VLSI microchip design
qsferaUUID: 914ce3de-e899-11ef-9a4b-732fbb2acc42
member: uid=lynn,ou=users,o=libregraph-idm
dn: cn=programmers,ou=groups,o=libregraph-idm
objectClass: groupOfNames
objectClass: qsferaObject
objectClass: top
cn: programmers
description: Computer Programmers
qsferaUUID: ce4aa240-dd94-11ef-82b8-4f4828849072
member: uid=alan,ou=users,o=libregraph-idm
member: uid=margaret,ou=users,o=libregraph-idm
member: uid=dennis,ou=users,o=libregraph-idm
member: uid=lynn,ou=users,o=libregraph-idm
+54
View File
@@ -0,0 +1,54 @@
package command
import (
"fmt"
"net/http"
"github.com/qsfera/server/pkg/config/configlog"
"github.com/qsfera/server/pkg/log"
"github.com/qsfera/server/services/idm/pkg/config"
"github.com/qsfera/server/services/idm/pkg/config/parser"
"github.com/spf13/cobra"
)
// Health is the entrypoint for the health command.
func Health(cfg *config.Config) *cobra.Command {
return &cobra.Command{
Use: "health",
Short: "check health status",
PreRunE: func(cmd *cobra.Command, args []string) error {
return configlog.ReturnError(parser.ParseConfig(cfg))
},
RunE: func(cmd *cobra.Command, args []string) error {
logger := log.Configure(cfg.Service.Name, cfg.Commons, cfg.LogLevel)
resp, err := http.Get(
fmt.Sprintf(
"http://%s/healthz",
cfg.Debug.Addr,
),
)
if err != nil {
logger.Fatal().
Err(err).
Msg("Failed to request health check")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
logger.Fatal().
Int("code", resp.StatusCode).
Msg("Health seems to be in bad state")
}
logger.Debug().
Int("code", resp.StatusCode).
Msg("Health got a good state")
return nil
},
}
}
+117
View File
@@ -0,0 +1,117 @@
package command
import (
"context"
"errors"
"fmt"
"os"
"syscall"
"time"
"github.com/qsfera/server/pkg/config/configlog"
"github.com/qsfera/server/pkg/log"
"github.com/qsfera/server/services/idm/pkg/config"
"github.com/qsfera/server/services/idm/pkg/config/parser"
"github.com/go-ldap/ldap/v3"
"github.com/libregraph/idm/pkg/ldbbolt"
"github.com/libregraph/idm/server"
"github.com/spf13/cobra"
bolt "go.etcd.io/bbolt"
"golang.org/x/term"
)
// ResetPassword is the entrypoint for the resetpassword command
func ResetPassword(cfg *config.Config) *cobra.Command {
resetPasswordCmd := &cobra.Command{
Use: "resetpassword",
Short: "Reset user password",
PreRunE: func(cmd *cobra.Command, args []string) error {
return configlog.ReturnFatal(parser.ParseConfig(cfg))
},
RunE: func(cmd *cobra.Command, args []string) error {
logger := log.Configure(cfg.Service.Name, cfg.Commons, cfg.LogLevel)
ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()
userNameFlag, _ := cmd.Flags().GetString("user-name")
return resetPassword(ctx, logger, cfg, userNameFlag)
},
}
resetPasswordCmd.Flags().StringP(
"user-name",
"u",
"admin",
"User name",
)
return resetPasswordCmd
}
func resetPassword(_ context.Context, logger log.Logger, cfg *config.Config, userName string) error {
servercfg := server.Config{
Logger: log.LogrusWrap(logger.Logger),
LDAPHandler: "boltdb",
LDAPBaseDN: "o=libregraph-idm",
BoltDBFile: cfg.IDM.DatabasePath,
}
userDN := fmt.Sprintf("uid=%s,ou=users,%s", userName, servercfg.LDAPBaseDN)
fmt.Printf("Resetting password for user '%s'.\n", userDN)
if _, err := os.Stat(servercfg.BoltDBFile); errors.Is(err, os.ErrNotExist) {
fmt.Fprintf(os.Stderr, "IDM database does not exist.\n")
return err
}
newPw, err := getPassword()
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading password: %v\n", err)
return err
}
bdb := &ldbbolt.LdbBolt{}
opts := bolt.Options{
Timeout: 1 * time.Millisecond,
}
if err := bdb.Configure(servercfg.Logger, servercfg.LDAPBaseDN, servercfg.BoltDBFile, &opts); err != nil {
fmt.Fprintf(os.Stderr, "Failed to open database: '%s'. Please stop any running КуСфера idm instance, as this tool requires exclusive access to the database.\n", err)
return err
}
defer bdb.Close()
if err := bdb.Initialize(); err != nil {
return err
}
pwRequest := ldap.NewPasswordModifyRequest(userDN, "", newPw)
if err := bdb.UpdatePassword(pwRequest); err != nil {
fmt.Fprintf(os.Stderr, "Failed to update user password: %v\n", err)
}
fmt.Printf("Password for user '%s' updated.\n", userDN)
return nil
}
func getPassword() (string, error) {
fmt.Print("Enter new password: ")
bytePassword, err := term.ReadPassword(syscall.Stdin)
if err != nil {
return "", err
}
fmt.Println("")
fmt.Print("Re-enter new password: ")
bytePasswordVerify, err := term.ReadPassword(syscall.Stdin)
if err != nil {
return "", err
}
fmt.Println("")
password := string(bytePassword)
passwordVerify := string(bytePasswordVerify)
if password != passwordVerify {
return "", errors.New("Passwords do not match")
}
return password, nil
}
+37
View File
@@ -0,0 +1,37 @@
package command
import (
"os"
"github.com/qsfera/server/pkg/clihelper"
"github.com/qsfera/server/services/idm/pkg/config"
"github.com/spf13/cobra"
)
// GetCommands provides all commands for this service
func GetCommands(cfg *config.Config) []*cobra.Command {
return []*cobra.Command{
// start this service
Server(cfg),
// interaction with this service
ResetPassword(cfg),
// infos about this service
Health(cfg),
Version(cfg),
}
}
// Execute is the entry point for the qsfera idm command.
func Execute(cfg *config.Config) error {
app := clihelper.DefaultApp(&cobra.Command{
Use: "idm",
Short: "Embedded LDAP service for КуСфера",
})
app.AddCommand(GetCommands(cfg)...)
app.SetArgs(os.Args[1:])
return app.ExecuteContext(cfg.Context)
}
+219
View File
@@ -0,0 +1,219 @@
package command
import (
"context"
"encoding/base64"
"errors"
"fmt"
"html/template"
"os"
"os/signal"
"strings"
"github.com/qsfera/server/pkg/config/configlog"
pkgcrypto "github.com/qsfera/server/pkg/crypto"
"github.com/qsfera/server/pkg/log"
"github.com/qsfera/server/pkg/runner"
"github.com/qsfera/server/services/idm"
"github.com/qsfera/server/services/idm/pkg/config"
"github.com/qsfera/server/services/idm/pkg/config/parser"
"github.com/qsfera/server/services/idm/pkg/server/debug"
"github.com/go-ldap/ldif"
"github.com/libregraph/idm/pkg/ldappassword"
"github.com/libregraph/idm/pkg/ldbbolt"
"github.com/libregraph/idm/server"
"github.com/spf13/cobra"
)
// Server is the entrypoint for the server command.
func Server(cfg *config.Config) *cobra.Command {
return &cobra.Command{
Use: "server",
Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name),
PreRunE: func(cmd *cobra.Command, args []string) error {
return configlog.ReturnFatal(parser.ParseConfig(cfg))
},
RunE: func(cmd *cobra.Command, args []string) error {
var cancel context.CancelFunc
if cfg.Context == nil {
cfg.Context, cancel = signal.NotifyContext(context.Background(), runner.StopSignals...)
defer cancel()
}
ctx := cfg.Context
logger := log.Configure(cfg.Service.Name, cfg.Commons, cfg.LogLevel)
gr := runner.NewGroup()
{
servercfg := server.Config{
Logger: log.LogrusWrap(logger.Logger),
LDAPHandler: "boltdb",
LDAPSListenAddr: cfg.IDM.LDAPSAddr,
TLSCertFile: cfg.IDM.Cert,
TLSKeyFile: cfg.IDM.Key,
LDAPBaseDN: "o=libregraph-idm",
LDAPAdminDN: "uid=libregraph,ou=sysusers,o=libregraph-idm",
BoltDBFile: cfg.IDM.DatabasePath,
}
if cfg.IDM.LDAPSAddr != "" {
// Generate a self-signing cert if no certificate is present
if err := pkgcrypto.GenCert(cfg.IDM.Cert, cfg.IDM.Key, logger); err != nil {
logger.Fatal().Err(err).Msgf("Could not generate test-certificate")
}
}
if _, err := os.Stat(servercfg.BoltDBFile); errors.Is(err, os.ErrNotExist) {
logger.Debug().Msg("Bootstrapping IDM database")
if err = bootstrap(logger, cfg, servercfg); err != nil {
logger.Error().Err(err).Msg("failed to bootstrap idm database")
}
}
svc, err := server.NewServer(&servercfg)
if err != nil {
return err
}
// we need an additional context for the idm server in order to
// cancel it anytime
svcCtx, svcCancel := context.WithCancel(ctx)
defer svcCancel()
gr.Add(runner.New(cfg.Service.Name+".svc", func() error {
return svc.Serve(svcCtx)
}, func() {
svcCancel()
}))
}
{
debugServer, err := debug.Server(
debug.Logger(logger),
debug.Context(ctx),
debug.Config(cfg),
)
if err != nil {
logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server")
return err
}
gr.Add(runner.NewGolangHttpServerRunner(cfg.Service.Name+".debug", debugServer))
}
grResults := gr.Run(ctx)
// return the first non-nil error found in the results
for _, grResult := range grResults {
if grResult.RunnerError != nil {
return grResult.RunnerError
}
}
return nil
},
}
}
func bootstrap(logger log.Logger, cfg *config.Config, srvcfg server.Config) error {
// Hash password if the config does not supply a hash already
var err error
type svcUser struct {
Name string
Password string
ID string
Issuer string
}
serviceUsers := []svcUser{
{
Name: "libregraph",
Password: cfg.ServiceUserPasswords.Idm,
},
{
Name: "idp",
Password: cfg.ServiceUserPasswords.Idp,
},
{
Name: "reva",
Password: cfg.ServiceUserPasswords.Reva,
},
}
if cfg.AdminUserID != "" {
serviceUsers = append(serviceUsers, svcUser{
Name: "admin",
Password: cfg.ServiceUserPasswords.OCAdmin,
ID: cfg.AdminUserID,
Issuer: cfg.DemoUsersIssuerUrl,
})
}
bdb := &ldbbolt.LdbBolt{}
if err := bdb.Configure(srvcfg.Logger, srvcfg.LDAPBaseDN, srvcfg.BoltDBFile, nil); err != nil {
return err
}
defer bdb.Close()
if err := bdb.Initialize(); err != nil {
return err
}
// Prepare the initial Data from template. To be able to set the
// supplied service user passwords
tmpl, err := template.New("baseldif").Parse(idm.BaseLDIF)
if err != nil {
return err
}
for i := range serviceUsers {
if strings.HasPrefix(serviceUsers[i].Password, "$argon2id$") {
// password is alread hashed
serviceUsers[i].Password = "{ARGON2}" + serviceUsers[i].Password
} else {
if serviceUsers[i].Password, err = ldappassword.Hash(serviceUsers[i].Password, "{ARGON2}"); err != nil {
return err
}
}
// We need to treat the hash as binary in the LDIF template to avoid
// go-ldap/ldif to do any fancy escaping
serviceUsers[i].Password = base64.StdEncoding.EncodeToString([]byte(serviceUsers[i].Password))
}
var tmplWriter strings.Builder
err = tmpl.Execute(&tmplWriter, serviceUsers)
if err != nil {
return err
}
bootstrapData := tmplWriter.String()
if cfg.CreateDemoUsers {
demoUsersTmpl, err := template.New("demousers").Parse(idm.DemoUsersLDIF)
if err != nil {
return err
}
var demoUsersWriter strings.Builder
err = demoUsersTmpl.Execute(&demoUsersWriter, cfg.DemoUsersIssuerUrl)
if err != nil {
return err
}
bootstrapData = bootstrapData + "\n" + demoUsersWriter.String()
}
s := strings.NewReader(bootstrapData)
lf := &ldif.LDIF{}
err = ldif.Unmarshal(s, lf)
if err != nil {
return err
}
for _, entry := range lf.AllEntries() {
logger.Debug().Str("dn", entry.DN).Msg("Adding entry")
if err := bdb.EntryPut(entry); err != nil {
return fmt.Errorf("error adding Entry '%s': %w", entry.DN, err)
}
}
return nil
}
@@ -0,0 +1,19 @@
package command
import (
"github.com/qsfera/server/services/idm/pkg/config"
"github.com/spf13/cobra"
)
// Version prints the service versions of all running instances.
func Version(cfg *config.Config) *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "print the version of this binary and the running service instances",
RunE: func(cmd *cobra.Command, args []string) error {
// not implemented
return nil
},
}
}
+40
View File
@@ -0,0 +1,40 @@
package config
import (
"context"
"github.com/qsfera/server/pkg/shared"
)
// Config combines all available configuration parts.
type Config struct {
Commons *shared.Commons `yaml:"-"` // don't use this directly as configuration for a service
Service Service `yaml:"-"`
LogLevel string `yaml:"loglevel" env:"OC_LOG_LEVEL;IDM_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"1.0.0"`
Debug Debug `yaml:"debug"`
IDM Settings `yaml:"idm"`
CreateDemoUsers bool `yaml:"create_demo_users" env:"IDM_CREATE_DEMO_USERS" desc:"Flag to enable or disable the creation of the demo users." introductionVersion:"1.0.0"`
DemoUsersIssuerUrl string `yaml:"demo_users_issuer_url" env:"OC_URL;OC_OIDC_ISSUER" desc:"The OIDC issuer URL to assign to the demo users." introductionVersion:"1.0.0"`
ServiceUserPasswords ServiceUserPasswords `yaml:"service_user_passwords"`
AdminUserID string `yaml:"admin_user_id" env:"OC_ADMIN_USER_ID;IDM_ADMIN_USER_ID" desc:"ID of the user that should receive admin privileges. Consider that the UUID can be encoded in some LDAP deployment configurations like in .ldif files. These need to be decoded beforehand." introductionVersion:"1.0.0"`
Context context.Context `yaml:"-"`
}
type Settings struct {
LDAPSAddr string `yaml:"ldaps_addr" env:"IDM_LDAPS_ADDR" desc:"Listen address for the LDAPS listener (ip-addr:port)." introductionVersion:"1.0.0"`
Cert string `yaml:"cert" env:"IDM_LDAPS_CERT" desc:"File name of the TLS server certificate for the LDAPS listener. If not defined, the root directory derives from $OC_BASE_DATA_PATH/idm." introductionVersion:"1.0.0"`
Key string `yaml:"key" env:"IDM_LDAPS_KEY" desc:"File name for the TLS certificate key for the server certificate. If not defined, the root directory derives from $OC_BASE_DATA_PATH/idm." introductionVersion:"1.0.0"`
DatabasePath string `yaml:"database" env:"IDM_DATABASE_PATH" desc:"Full path to the IDM backend database. If not defined, the root directory derives from $OC_BASE_DATA_PATH/idm." introductionVersion:"1.0.0"`
}
type ServiceUserPasswords struct {
OCAdmin string `yaml:"admin_password" env:"IDM_ADMIN_PASSWORD" desc:"Password to set for the КуСфера 'admin' user. Either cleartext or an argon2id hash." introductionVersion:"1.0.0"`
Idm string `yaml:"idm_password" env:"IDM_SVC_PASSWORD" desc:"Password to set for the 'idm' service user. Either cleartext or an argon2id hash." introductionVersion:"1.0.0"`
Reva string `yaml:"reva_password" env:"IDM_REVASVC_PASSWORD" desc:"Password to set for the 'reva' service user. Either cleartext or an argon2id hash." introductionVersion:"1.0.0"`
Idp string `yaml:"idp_password" env:"IDM_IDPSVC_PASSWORD" desc:"Password to set for the 'idp' service user. Either cleartext or an argon2id hash." introductionVersion:"1.0.0"`
}
+9
View File
@@ -0,0 +1,9 @@
package config
// Debug defines the available debug configuration.
type Debug struct {
Addr string `yaml:"addr" env:"IDM_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"1.0.0"`
Token string `yaml:"token" env:"IDM_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"1.0.0"`
Pprof bool `yaml:"pprof" env:"IDM_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"1.0.0"`
Zpages bool `yaml:"zpages" env:"IDM_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces." introductionVersion:"1.0.0"`
}
@@ -0,0 +1,55 @@
package defaults
import (
"path"
"github.com/qsfera/server/pkg/config/defaults"
"github.com/qsfera/server/services/idm/pkg/config"
)
// FullDefaultConfig returns a fully initialized default configuration
func FullDefaultConfig() *config.Config {
cfg := DefaultConfig()
EnsureDefaults(cfg)
Sanitize(cfg)
return cfg
}
// DefaultConfig returns a basic default configuration
func DefaultConfig() *config.Config {
return &config.Config{
Debug: config.Debug{
Addr: "127.0.0.1:9239",
Token: "",
Pprof: false,
Zpages: false,
},
Service: config.Service{
Name: "idm",
},
CreateDemoUsers: false,
DemoUsersIssuerUrl: "https://localhost:9200",
IDM: config.Settings{
LDAPSAddr: "127.0.0.1:9235",
Cert: path.Join(defaults.BaseDataPath(), "idm", "ldap.crt"),
Key: path.Join(defaults.BaseDataPath(), "idm", "ldap.key"),
DatabasePath: path.Join(defaults.BaseDataPath(), "idm", "idm.boltdb"),
},
}
}
// EnsureDefaults adds default values to the configuration if they are not set yet
func EnsureDefaults(cfg *config.Config) {
if cfg.LogLevel == "" {
cfg.LogLevel = "error"
}
if cfg.AdminUserID == "" && cfg.Commons != nil {
cfg.AdminUserID = cfg.Commons.AdminUserID
}
}
// Sanitize sanitizes the configuration
func Sanitize(cfg *config.Config) {
// nothing to sanitize here
}
@@ -0,0 +1,57 @@
package parser
import (
"errors"
occfg "github.com/qsfera/server/pkg/config"
"github.com/qsfera/server/pkg/shared"
"github.com/qsfera/server/services/idm/pkg/config"
"github.com/qsfera/server/services/idm/pkg/config/defaults"
"github.com/qsfera/server/pkg/config/envdecode"
)
// ParseConfig loads configuration from known paths.
func ParseConfig(cfg *config.Config) error {
err := occfg.BindSourcesToStructs(cfg.Service.Name, cfg)
if err != nil {
return err
}
defaults.EnsureDefaults(cfg)
// load all env variables relevant to the config in the current context.
if err := envdecode.Decode(cfg); err != nil {
// no environment variable set for this config is an expected "error"
if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) {
return err
}
}
defaults.Sanitize(cfg)
return Validate(cfg)
}
func Validate(cfg *config.Config) error {
if cfg.CreateDemoUsers && cfg.AdminUserID == "" {
return shared.MissingAdminUserID(cfg.Service.Name)
}
if cfg.ServiceUserPasswords.Idm == "" {
return shared.MissingServiceUserPassword(cfg.Service.Name, "IDM")
}
if cfg.AdminUserID != "" && cfg.ServiceUserPasswords.OCAdmin == "" {
return shared.MissingServiceUserPassword(cfg.Service.Name, "admin")
}
if cfg.ServiceUserPasswords.Idp == "" {
return shared.MissingServiceUserPassword(cfg.Service.Name, "IDP")
}
if cfg.ServiceUserPasswords.Reva == "" {
return shared.MissingServiceUserPassword(cfg.Service.Name, "REVA")
}
return nil
}
@@ -0,0 +1,6 @@
package config
// Service defines the available service configuration.
type Service struct {
Name string `yaml:"-"`
}
@@ -0,0 +1,50 @@
package debug
import (
"context"
"github.com/qsfera/server/pkg/log"
"github.com/qsfera/server/services/idm/pkg/config"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
@@ -0,0 +1,23 @@
package debug
import (
"net/http"
"github.com/qsfera/server/pkg/service/debug"
"github.com/qsfera/server/pkg/version"
)
// Server initializes the debug service and server.
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
return debug.NewService(
debug.Logger(options.Logger),
debug.Name(options.Config.Service.Name),
debug.Version(version.GetString()),
debug.Address(options.Config.Debug.Addr),
debug.Token(options.Config.Debug.Token),
debug.Pprof(options.Config.Debug.Pprof),
debug.Zpages(options.Config.Debug.Zpages),
), nil
}