Add embeded libregrah/idm server

This add a new service "idm" providing and LDAP service (via ldaps) on
port 9235.  If not existing it will bootstrap an initial LDAP tree and
administrative user as well as a self-signed Certificate and Key
(similar to what is done for glauth).
This commit is contained in:
Ralf Haferkamp
2022-02-17 09:23:17 +01:00
parent a3aad659f8
commit 02775b72c7
24 changed files with 651 additions and 6 deletions

View File

@@ -74,7 +74,7 @@ ci-golangci-lint: $(GOLANGCI_LINT)
.PHONY: test
test:
@go test -v -coverprofile coverage.out ./...
@go test -v -tags '$(TAGS)' -coverprofile coverage.out ./...
.PHONY: go-coverage
go-coverage:

9
go.mod
View File

@@ -29,6 +29,7 @@ require (
github.com/go-chi/cors v1.2.0
github.com/go-chi/render v1.0.1
github.com/go-ldap/ldap/v3 v3.4.2
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3
github.com/go-logr/logr v1.2.2
github.com/go-ozzo/ozzo-validation/v4 v4.3.0
github.com/gofrs/uuid v4.2.0+incompatible
@@ -40,7 +41,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3
github.com/iancoleman/strcase v0.2.0
github.com/justinas/alice v1.2.0
github.com/libregraph/idm v0.3.1-0.20220120080913-d0003ade935e
github.com/libregraph/idm v0.3.1-0.20220222123017-f9d520ac1f11
github.com/libregraph/lico v0.53.1
github.com/mennanov/fieldmask-utils v0.5.0
github.com/mitchellh/mapstructure v1.4.3
@@ -91,7 +92,10 @@ require (
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 // indirect
github.com/RoaringBitmap/roaring v0.9.4 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 // indirect
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 // indirect
github.com/armon/go-metrics v0.3.10 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/aws/aws-sdk-go v1.42.39 // indirect
github.com/beevik/etree v1.1.0 // indirect
@@ -129,6 +133,7 @@ require (
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/eternnoir/gncp v0.0.0-20170707042257-c70df2d0cd68 // indirect
@@ -235,9 +240,11 @@ require (
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
github.com/sony/gobreaker v0.5.0 // indirect
github.com/spacewander/go-suffix-tree v0.0.0-20191010040751-0865e368c784 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/steveyen/gtreap v0.1.0 // indirect
github.com/stretchr/objx v0.3.0 // indirect
github.com/trustelem/zxcvbn v1.0.1 // indirect
github.com/tus/tusd v1.8.0 // indirect
github.com/wk8/go-ordered-map v0.2.0 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect

15
go.sum
View File

@@ -134,9 +134,11 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I=
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.976/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/amoghe/go-crypt v0.0.0-20191109212615-b2ff80594b7f/go.mod h1:eFiR01PwTcpbzXtdMces7zxg6utvFM5puiWHpWB8D/k=
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 h1:I9YN9WMo3SUh7p/4wKeNvD/IQla3U3SUa61U7ul+xM4=
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964/go.mod h1:eFiR01PwTcpbzXtdMces7zxg6utvFM5puiWHpWB8D/k=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
@@ -153,6 +155,7 @@ github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQh
github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
@@ -364,6 +367,7 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
@@ -461,6 +465,7 @@ github.com/go-ldap/ldap/v3 v3.1.7/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd
github.com/go-ldap/ldap/v3 v3.4.1/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
github.com/go-ldap/ldap/v3 v3.4.2 h1:zFZKcXKLqZpFMrMQGHeHWKXbDTdNCmhGY9AK41zPh+8=
github.com/go-ldap/ldap/v3 v3.4.2/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3 h1:sfz1YppV05y4sYaW7kXZtrocU/+vimnIWt4cxAYh7+o=
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3/go.mod h1:ZXFhGda43Z2TVbfGZefXyMJzsDHhCh0go3bZUcwTx7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -933,8 +938,8 @@ github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libregraph/idm v0.3.1-0.20220120080913-d0003ade935e h1:iO7Gf/cMCn2sn28CoWsHUQ/O7VfH0DAJrhHyTE/xQk4=
github.com/libregraph/idm v0.3.1-0.20220120080913-d0003ade935e/go.mod h1:gdDXfHsvrrmoNwqcXroOGIi2+Lrx5iAipbpVwR7goeY=
github.com/libregraph/idm v0.3.1-0.20220222123017-f9d520ac1f11 h1:ReFrghRLQhQdt5BW7HXtEzySmsgUCXL1U36gnmka73U=
github.com/libregraph/idm v0.3.1-0.20220222123017-f9d520ac1f11/go.mod h1:wB8ApGAQsXDXXGWEeFimK/3q8Z/jRa+m3nqnvoYjBsQ=
github.com/libregraph/lico v0.53.1 h1:c5JYFg0mrIgk6Ktj77rNJ2K8X01hs28JJ9Vw1N1iMAk=
github.com/libregraph/lico v0.53.1/go.mod h1:EXf6Y9s9TygW6unSXosBpVXWUURoN2sPX0053o4zVfQ=
github.com/linode/linodego v0.25.3/go.mod h1:GSBKPpjoQfxEfryoCRcgkuUOCuVtGHWhzI8OMdycNTE=
@@ -1198,7 +1203,6 @@ github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQ
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -1321,6 +1325,7 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spacewander/go-suffix-tree v0.0.0-20191010040751-0865e368c784 h1:0jjO3HdJfOn6gYHD/ZNZh0LLMxEAqkYX7xoDPQReEgs=
github.com/spacewander/go-suffix-tree v0.0.0-20191010040751-0865e368c784/go.mod h1:ff/5myEGgtsAwf26goQCO905GrEm5ugEZSd6OWTsrhM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@@ -1368,6 +1373,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
@@ -1377,6 +1383,7 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
github.com/trustelem/zxcvbn v1.0.1 h1:mp4JFtzdDYGj9WYSD3KQSkwwUumWNFzXaAjckaTYpsc=
github.com/trustelem/zxcvbn v1.0.1/go.mod h1:zonUyKeh7sw6psPf/e3DtRqkRyZvAbOfjNz/aO7YQ5s=
github.com/tus/tusd v1.1.0/go.mod h1:3DWPOdeCnjBwKtv98y5dSws3itPqfce5TVa0s59LRiA=
github.com/tus/tusd v1.8.0 h1:QODQ5uMhL2tFX3Ouk7rUHHqPqeDBvi2+gYIoyUO0n8Q=

32
idm/Makefile Normal file
View File

@@ -0,0 +1,32 @@
SHELL := bash
NAME := ocs
TAGS := disable_crypt
include ../.make/recursion.mk
############ tooling ############
ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
include ../.bingo/Variables.mk
endif
############ go tooling ############
include ../.make/go.mk
############ release ############
include ../.make/release.mk
############ docs generate ############
include ../.make/docs.mk
.PHONY: docs-generate
docs-generate: config-docs-generate
############ generate ############
include ../.make/generate.mk
.PHONY: ci-go-generate
ci-go-generate: # CI runs ci-node-generate automatically before this target
.PHONY: ci-node-generate
ci-node-generate:

14
idm/cmd/idm/main.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"os"
"github.com/owncloud/ocis/idm/pkg/command"
"github.com/owncloud/ocis/idm/pkg/config"
)
func main() {
if err := command.Execute(config.DefaultConfig()); err != nil {
os.Exit(1)
}
}

53
idm/pkg/command/health.go Normal file
View File

@@ -0,0 +1,53 @@
package command
import (
"fmt"
"net/http"
"github.com/owncloud/ocis/idm/pkg/config"
"github.com/owncloud/ocis/idm/pkg/config/parser"
"github.com/owncloud/ocis/idm/pkg/logging"
"github.com/urfave/cli/v2"
)
// Health is the entrypoint for the health command.
func Health(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "health",
Usage: "check health status",
Category: "info",
Before: func(c *cli.Context) error {
return parser.ParseConfig(cfg)
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
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
},
}
}

64
idm/pkg/command/root.go Normal file
View File

@@ -0,0 +1,64 @@
package command
import (
"context"
"os"
"github.com/owncloud/ocis/idm/pkg/config"
"github.com/owncloud/ocis/ocis-pkg/clihelper"
ociscfg "github.com/owncloud/ocis/ocis-pkg/config"
"github.com/thejerf/suture/v4"
"github.com/urfave/cli/v2"
)
// GetCommands provides all commands for this service
func GetCommands(cfg *config.Config) cli.Commands {
return []*cli.Command{
// start this service
Server(cfg),
// interaction with this service
// infos about this service
Health(cfg),
Version(cfg),
}
}
// Execute is the entry point for the ocis-idm command.
func Execute(cfg *config.Config) error {
app := clihelper.DefaultApp(&cli.App{
Name: "ocis-idm",
Usage: "Embedded LDAP service for oCIS",
Commands: GetCommands(cfg),
})
cli.HelpFlag = &cli.BoolFlag{
Name: "help,h",
Usage: "Show the help",
}
return app.Run(os.Args)
}
// SutureService allows for the idm command to be embedded and supervised by a suture supervisor tree.
type SutureService struct {
cfg *config.Config
}
// NewSutureService creates a new idm.SutureService
func NewSutureService(cfg *ociscfg.Config) suture.Service {
cfg.IDM.Commons = cfg.Commons
return SutureService{
cfg: cfg.IDM,
}
}
func (s SutureService) Serve(ctx context.Context) error {
s.cfg.Context = ctx
if err := Execute(s.cfg); err != nil {
return err
}
return nil
}

153
idm/pkg/command/server.go Normal file
View File

@@ -0,0 +1,153 @@
package command
import (
"context"
"encoding/base64"
"errors"
"fmt"
"html/template"
"os"
"strings"
"github.com/go-ldap/ldif"
"github.com/libregraph/idm/pkg/ldappassword"
"github.com/libregraph/idm/pkg/ldbbolt"
"github.com/libregraph/idm/server"
"github.com/owncloud/ocis/idm/pkg/config"
"github.com/owncloud/ocis/idm/pkg/config/parser"
"github.com/owncloud/ocis/idm/pkg/logging"
pkgcrypto "github.com/owncloud/ocis/ocis-pkg/crypto"
"github.com/owncloud/ocis/ocis-pkg/log"
"github.com/urfave/cli/v2"
)
// Server is the entrypoint for the server command.
func Server(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "server",
Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name),
Category: "server",
Before: func(c *cli.Context) error {
return parser.ParseConfig(cfg)
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
ctx, cancel := func() (context.Context, context.CancelFunc) {
if cfg.Context == nil {
return context.WithCancel(context.Background())
}
return context.WithCancel(cfg.Context)
}()
defer cancel()
start(ctx, logger, cfg)
return nil
},
}
}
func start(ctx context.Context, logger log.Logger, cfg *config.Config) error {
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=libregrah,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")
err = bootstrap(logger, cfg, servercfg)
logger.Error().Err(err).Msg("failed")
}
svc, err := server.NewServer(&servercfg)
if err != nil {
return err
}
return svc.Serve(ctx)
}
func bootstrap(logger log.Logger, cfg *config.Config, srvcfg server.Config) error {
// Hash password if the config does not supply a hash already
var pwhash string
var err error
if strings.HasPrefix(cfg.IDM.AdminPassword, "$argon2id$") {
// password is alread hashed
pwhash = "{ARGON2}" + cfg.IDM.AdminPassword
} else {
if pwhash, err = ldappassword.Hash(cfg.IDM.AdminPassword, "{ARGON2}"); err != nil {
return err
}
}
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 admin password
tmpl, err := template.New("baseldif").Parse(baseldif)
if err != nil {
return err
}
var tmplWriter strings.Builder
// We need to treat the hash as binary in the LDIF template to avoid
// go-ldap/ldif to to any fancy escaping
b64 := base64.StdEncoding.EncodeToString([]byte(pwhash))
err = tmpl.Execute(&tmplWriter, b64)
if err != nil {
return err
}
s := strings.NewReader(tmplWriter.String())
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
}
var baseldif string = `dn: o=libregraph-idm
o: libregraph-idm
objectClass: organization
dn: ou=users,o=libregraph-idm
objectClass: organizationalUnit
ou: users
dn: ou=groups,o=libregraph-idm
objectClass: organizationalUnit
ou: groups
dn: uid=libregraph,o=libregraph-idm
objectClass: account
objectClass: simpleSecurityObject
uid: libregraph
userPassword:: {{.}}`

View File

@@ -0,0 +1,19 @@
package command
import (
"github.com/owncloud/ocis/idm/pkg/config"
"github.com/urfave/cli/v2"
)
// Version prints the service versions of all running instances.
func Version(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "version",
Usage: "print the version of this binary and the running extension instances",
Category: "info",
Action: func(c *cli.Context) error {
// not implemented
return nil
},
}
}

30
idm/pkg/config/config.go Normal file
View File

@@ -0,0 +1,30 @@
package config
import (
"context"
"github.com/owncloud/ocis/ocis-pkg/shared"
)
// Config combines all available configuration parts.
type Config struct {
*shared.Commons
Service Service
Tracing *Tracing `ocisConfig:"tracing"`
Log *Log `ocisConfig:"log"`
Debug Debug `ocisConfig:"debug"`
IDM Settings `ocisConfig:"idm"`
Context context.Context
}
type Settings struct {
LDAPSAddr string `ocisConfig:"ldaps_addr" env:"IDM_LDAPS_ADDR"`
Cert string `ocisConfig:"cert" env:"IDM_LDAPS_CERT"`
Key string `ocisConfig:"cert" env:"IDM_LDAPS_KEY"`
DatabasePath string `ocisConfig:"database" env:"IDM_DATABASE_PATH"`
AdminPassword string `ocisConfig:"admin_password" env:"IDM_ADMIN_PASSWORD"`
}

9
idm/pkg/config/debug.go Normal file
View File

@@ -0,0 +1,9 @@
package config
// Debug defines the available debug configuration.
type Debug struct {
Addr string `ocisConfig:"addr" env:"IDM_DEBUG_ADDR"`
Token string `ocisConfig:"token" env:"IDM_DEBUG_TOKEN"`
Pprof bool `ocisConfig:"pprof" env:"IDM_DEBUG_PPROF"`
Zpages bool `ocisConfig:"zpages" env:"IDM_DEBUG_ZPAGES"`
}

View File

@@ -0,0 +1,22 @@
package config
import (
"path"
"github.com/owncloud/ocis/ocis-pkg/config/defaults"
)
func DefaultConfig() *Config {
return &Config{
Service: Service{
Name: "idm",
},
IDM: 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", "ocis.boltdb"),
AdminPassword: "admin",
},
}
}

9
idm/pkg/config/log.go Normal file
View File

@@ -0,0 +1,9 @@
package config
// Log defines the available log configuration.
type Log struct {
Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;IDM_LOG_LEVEL"`
Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;IDM_LOG_PRETTY"`
Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;IDM_LOG_COLOR"`
File string `mapstructure:"file" env:"OCIS_LOG_FILE;IDM_LOG_FILE"`
}

View File

@@ -0,0 +1,51 @@
package parser
import (
"errors"
"github.com/owncloud/ocis/idm/pkg/config"
ociscfg "github.com/owncloud/ocis/ocis-pkg/config"
"github.com/owncloud/ocis/ocis-pkg/config/envdecode"
)
// ParseConfig loads accounts configuration from known paths.
func ParseConfig(cfg *config.Config) error {
_, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg)
if err != nil {
return err
}
// provide with defaults for shared logging, since we need a valid destination address for BindEnv.
if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil {
cfg.Log = &config.Log{
Level: cfg.Commons.Log.Level,
Pretty: cfg.Commons.Log.Pretty,
Color: cfg.Commons.Log.Color,
File: cfg.Commons.Log.File,
}
} else if cfg.Log == nil {
cfg.Log = &config.Log{}
}
// provide with defaults for shared tracing, since we need a valid destination address for BindEnv.
if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil {
cfg.Tracing = &config.Tracing{
Enabled: cfg.Commons.Tracing.Enabled,
Type: cfg.Commons.Tracing.Type,
Endpoint: cfg.Commons.Tracing.Endpoint,
Collector: cfg.Commons.Tracing.Collector,
}
} else if cfg.Tracing == nil {
cfg.Tracing = &config.Tracing{}
}
// 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
}
}
return nil
}

View File

@@ -0,0 +1,6 @@
package config
// Service defines the available service configuration.
type Service struct {
Name string
}

View File

@@ -0,0 +1,9 @@
package config
// Tracing defines the available tracing configuration.
type Tracing struct {
Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;IDM_TRACING_ENABLED"`
Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;IDM_TRACING_TYPE"`
Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;IDM_TRACING_ENDPOINT"`
Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;IDM_TRACING_COLLECTOR"`
}

View File

@@ -0,0 +1,17 @@
package logging
import (
"github.com/owncloud/ocis/idm/pkg/config"
"github.com/owncloud/ocis/ocis-pkg/log"
)
// LoggerFromConfig initializes a service-specific logger instance.
func Configure(name string, cfg *config.Log) log.Logger {
return log.NewLogger(
log.Name(name),
log.Level(cfg.Level),
log.Pretty(cfg.Pretty),
log.Color(cfg.Color),
log.File(cfg.File),
)
}

View File

@@ -0,0 +1,50 @@
package debug
import (
"context"
"github.com/owncloud/ocis/idm/pkg/config"
"github.com/owncloud/ocis/ocis-pkg/log"
)
// 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
}
}

View File

@@ -0,0 +1,59 @@
package debug
import (
"io"
"net/http"
"github.com/owncloud/ocis/idm/pkg/config"
"github.com/owncloud/ocis/ocis-pkg/service/debug"
"github.com/owncloud/ocis/ocis-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.String),
debug.Address(options.Config.Debug.Addr),
debug.Token(options.Config.Debug.Token),
debug.Pprof(options.Config.Debug.Pprof),
debug.Zpages(options.Config.Debug.Zpages),
debug.Health(health(options.Config)),
debug.Ready(ready(options.Config)),
), nil
}
// health implements the health check.
func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO: check if services are up and running
_, err := io.WriteString(w, http.StatusText(http.StatusOK))
// io.WriteString should not fail but if it does we want to know.
if err != nil {
panic(err)
}
}
}
// ready implements the ready check.
func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO: check if services are up and running
_, err := io.WriteString(w, http.StatusText(http.StatusOK))
// io.WriteString should not fail but if it does we want to know.
if err != nil {
panic(err)
}
}
}

View File

@@ -7,6 +7,7 @@ import (
glauth "github.com/owncloud/ocis/glauth/pkg/config"
graphExplorer "github.com/owncloud/ocis/graph-explorer/pkg/config"
graph "github.com/owncloud/ocis/graph/pkg/config"
idm "github.com/owncloud/ocis/idm/pkg/config"
idp "github.com/owncloud/ocis/idp/pkg/config"
nats "github.com/owncloud/ocis/nats/pkg/config"
notifications "github.com/owncloud/ocis/notifications/pkg/config"
@@ -62,6 +63,7 @@ type Config struct {
Graph *graph.Config `ocisConfig:"graph"`
GraphExplorer *graphExplorer.Config `ocisConfig:"graph_explorer"`
IDP *idp.Config `ocisConfig:"idp"`
IDM *idm.Config `ocisConfig:"idm"`
Nats *nats.Config `ocisConfig:"nats"`
Notifications *notifications.Config `ocisConfig:"notifications"`
OCS *ocs.Config `ocisConfig:"ocs"`

View File

@@ -5,6 +5,7 @@ import (
glauth "github.com/owncloud/ocis/glauth/pkg/config"
graphExplorer "github.com/owncloud/ocis/graph-explorer/pkg/config"
graph "github.com/owncloud/ocis/graph/pkg/config"
idm "github.com/owncloud/ocis/idm/pkg/config"
idp "github.com/owncloud/ocis/idp/pkg/config"
nats "github.com/owncloud/ocis/nats/pkg/config"
notifications "github.com/owncloud/ocis/notifications/pkg/config"
@@ -31,6 +32,7 @@ func DefaultConfig() *Config {
GLAuth: glauth.DefaultConfig(),
Graph: graph.DefaultConfig(),
IDP: idp.DefaultConfig(),
IDM: idm.DefaultConfig(),
Nats: nats.DefaultConfig(),
Notifications: notifications.DefaultConfig(),
Proxy: proxy.DefaultConfig(),

View File

@@ -1,6 +1,8 @@
SHELL := bash
NAME := ocis
TAGS := disable_crypt
include ../.make/recursion.mk
############ tooling ############

26
ocis/pkg/command/idm.go Normal file
View File

@@ -0,0 +1,26 @@
package command
import (
"github.com/owncloud/ocis/idm/pkg/command"
"github.com/owncloud/ocis/ocis-pkg/config"
"github.com/owncloud/ocis/ocis-pkg/config/parser"
"github.com/owncloud/ocis/ocis/pkg/register"
"github.com/urfave/cli/v2"
)
// IDMCommand is the entrypoint for the idm server command.
func IDMCommand(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "idm",
Usage: "idm extension commands",
Category: "extensions",
Before: func(ctx *cli.Context) error {
return parser.ParseConfig(cfg)
},
Subcommands: command.GetCommands(cfg.IDM),
}
}
func init() {
register.AddCommand(IDMCommand)
}

View File

@@ -22,6 +22,7 @@ import (
glauth "github.com/owncloud/ocis/glauth/pkg/command"
graphExplorer "github.com/owncloud/ocis/graph-explorer/pkg/command"
graph "github.com/owncloud/ocis/graph/pkg/command"
idm "github.com/owncloud/ocis/idm/pkg/command"
idp "github.com/owncloud/ocis/idp/pkg/command"
nats "github.com/owncloud/ocis/nats/pkg/command"
notifications "github.com/owncloud/ocis/notifications/pkg/command"
@@ -99,6 +100,7 @@ func NewService(options ...Option) (*Service, error) {
s.ServicesRegistry["graph"] = graph.NewSutureService
s.ServicesRegistry["graph-explorer"] = graphExplorer.NewSutureService
s.ServicesRegistry["idp"] = idp.NewSutureService
s.ServicesRegistry["idm"] = idm.NewSutureService
s.ServicesRegistry["ocs"] = ocs.NewSutureService
s.ServicesRegistry["store"] = store.NewSutureService
s.ServicesRegistry["thumbnails"] = thumbnails.NewSutureService