From 02775b72c77e4dd314213d3fd3bd38c92c3aa2b3 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Thu, 17 Feb 2022 09:23:17 +0100 Subject: [PATCH] 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). --- .make/go.mk | 2 +- go.mod | 9 +- go.sum | 15 ++- idm/Makefile | 32 ++++++ idm/cmd/idm/main.go | 14 +++ idm/pkg/command/health.go | 53 ++++++++++ idm/pkg/command/root.go | 64 ++++++++++++ idm/pkg/command/server.go | 153 ++++++++++++++++++++++++++++ idm/pkg/command/version.go | 19 ++++ idm/pkg/config/config.go | 30 ++++++ idm/pkg/config/debug.go | 9 ++ idm/pkg/config/defaultconfig.go | 22 ++++ idm/pkg/config/log.go | 9 ++ idm/pkg/config/parser/parse.go | 51 ++++++++++ idm/pkg/config/service.go | 6 ++ idm/pkg/config/tracing.go | 9 ++ idm/pkg/logging/logging.go | 17 ++++ idm/pkg/server/debug/option.go | 50 +++++++++ idm/pkg/server/debug/server.go | 59 +++++++++++ ocis-pkg/config/config.go | 2 + ocis-pkg/config/defaultconfig.go | 2 + ocis/Makefile | 2 + ocis/pkg/command/idm.go | 26 +++++ ocis/pkg/runtime/service/service.go | 2 + 24 files changed, 651 insertions(+), 6 deletions(-) create mode 100644 idm/Makefile create mode 100644 idm/cmd/idm/main.go create mode 100644 idm/pkg/command/health.go create mode 100644 idm/pkg/command/root.go create mode 100644 idm/pkg/command/server.go create mode 100644 idm/pkg/command/version.go create mode 100644 idm/pkg/config/config.go create mode 100644 idm/pkg/config/debug.go create mode 100644 idm/pkg/config/defaultconfig.go create mode 100644 idm/pkg/config/log.go create mode 100644 idm/pkg/config/parser/parse.go create mode 100644 idm/pkg/config/service.go create mode 100644 idm/pkg/config/tracing.go create mode 100644 idm/pkg/logging/logging.go create mode 100644 idm/pkg/server/debug/option.go create mode 100644 idm/pkg/server/debug/server.go create mode 100644 ocis/pkg/command/idm.go diff --git a/.make/go.mk b/.make/go.mk index 2ac312ceed..b2af851fdc 100644 --- a/.make/go.mk +++ b/.make/go.mk @@ -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: diff --git a/go.mod b/go.mod index 49f837a598..e574b07085 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 4557a5b88d..4a2a807e69 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/idm/Makefile b/idm/Makefile new file mode 100644 index 0000000000..7eb26a3ecb --- /dev/null +++ b/idm/Makefile @@ -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: diff --git a/idm/cmd/idm/main.go b/idm/cmd/idm/main.go new file mode 100644 index 0000000000..680d58dafb --- /dev/null +++ b/idm/cmd/idm/main.go @@ -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) + } +} diff --git a/idm/pkg/command/health.go b/idm/pkg/command/health.go new file mode 100644 index 0000000000..2de74ad128 --- /dev/null +++ b/idm/pkg/command/health.go @@ -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 + }, + } +} diff --git a/idm/pkg/command/root.go b/idm/pkg/command/root.go new file mode 100644 index 0000000000..2be9e0a177 --- /dev/null +++ b/idm/pkg/command/root.go @@ -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 +} diff --git a/idm/pkg/command/server.go b/idm/pkg/command/server.go new file mode 100644 index 0000000000..eee544ac1f --- /dev/null +++ b/idm/pkg/command/server.go @@ -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:: {{.}}` diff --git a/idm/pkg/command/version.go b/idm/pkg/command/version.go new file mode 100644 index 0000000000..4a6faa0c96 --- /dev/null +++ b/idm/pkg/command/version.go @@ -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 + }, + } +} diff --git a/idm/pkg/config/config.go b/idm/pkg/config/config.go new file mode 100644 index 0000000000..da0fd1bcab --- /dev/null +++ b/idm/pkg/config/config.go @@ -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"` +} diff --git a/idm/pkg/config/debug.go b/idm/pkg/config/debug.go new file mode 100644 index 0000000000..c90432afac --- /dev/null +++ b/idm/pkg/config/debug.go @@ -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"` +} diff --git a/idm/pkg/config/defaultconfig.go b/idm/pkg/config/defaultconfig.go new file mode 100644 index 0000000000..aaec93bdea --- /dev/null +++ b/idm/pkg/config/defaultconfig.go @@ -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", + }, + } +} diff --git a/idm/pkg/config/log.go b/idm/pkg/config/log.go new file mode 100644 index 0000000000..780628b79d --- /dev/null +++ b/idm/pkg/config/log.go @@ -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"` +} diff --git a/idm/pkg/config/parser/parse.go b/idm/pkg/config/parser/parse.go new file mode 100644 index 0000000000..22b3429cc1 --- /dev/null +++ b/idm/pkg/config/parser/parse.go @@ -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 +} diff --git a/idm/pkg/config/service.go b/idm/pkg/config/service.go new file mode 100644 index 0000000000..f98aa3d27e --- /dev/null +++ b/idm/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/idm/pkg/config/tracing.go b/idm/pkg/config/tracing.go new file mode 100644 index 0000000000..df9b377e1f --- /dev/null +++ b/idm/pkg/config/tracing.go @@ -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"` +} diff --git a/idm/pkg/logging/logging.go b/idm/pkg/logging/logging.go new file mode 100644 index 0000000000..b43451cf7f --- /dev/null +++ b/idm/pkg/logging/logging.go @@ -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), + ) +} diff --git a/idm/pkg/server/debug/option.go b/idm/pkg/server/debug/option.go new file mode 100644 index 0000000000..1fbc904654 --- /dev/null +++ b/idm/pkg/server/debug/option.go @@ -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 + } +} diff --git a/idm/pkg/server/debug/server.go b/idm/pkg/server/debug/server.go new file mode 100644 index 0000000000..426f95a623 --- /dev/null +++ b/idm/pkg/server/debug/server.go @@ -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) + } + } +} diff --git a/ocis-pkg/config/config.go b/ocis-pkg/config/config.go index 66707055a6..2f0495b1f8 100644 --- a/ocis-pkg/config/config.go +++ b/ocis-pkg/config/config.go @@ -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"` diff --git a/ocis-pkg/config/defaultconfig.go b/ocis-pkg/config/defaultconfig.go index 60eea86a67..222b04b0fb 100644 --- a/ocis-pkg/config/defaultconfig.go +++ b/ocis-pkg/config/defaultconfig.go @@ -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(), diff --git a/ocis/Makefile b/ocis/Makefile index 4608380188..94d351a04a 100644 --- a/ocis/Makefile +++ b/ocis/Makefile @@ -1,6 +1,8 @@ SHELL := bash NAME := ocis +TAGS := disable_crypt + include ../.make/recursion.mk ############ tooling ############ diff --git a/ocis/pkg/command/idm.go b/ocis/pkg/command/idm.go new file mode 100644 index 0000000000..fbfa4ba069 --- /dev/null +++ b/ocis/pkg/command/idm.go @@ -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) +} diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index fa1a129088..c055f31046 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -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