From aa5636aff4b842215af4a02d2fea9b7b9397080f Mon Sep 17 00:00:00 2001 From: Sean Trantalis Date: Mon, 12 Aug 2024 14:51:08 -0400 Subject: [PATCH] feat(core): ability to run a set of isolated services (#1245) This pr resolves https://github.com/opentdf/platform/issues/1033 It introduces two new config values `mode` and `sdk_config` at the root level. It also removes the `enabled` field on a service. Mode now takes a list which could be `all`, `core` or a specific namespaced service for example `kas`. You can also combine modes like `core,kas`. When running in something other than `core` or `all` you are required to specify a `sdk_config` object. This is because the core services will be remote now and not over the internal IPC server. --- docker-compose.yaml | 3 +- go.work.sum | 22 +- opentdf-dev.yaml | 5 - opentdf-example-no-kas.yaml | 6 +- opentdf-example.yaml | 6 +- service/authorization/authorization.go | 4 +- service/cmd/start.go | 1 + service/entityresolution/entityresolution.go | 2 +- service/go.mod | 17 +- service/go.sum | 28 +- service/internal/config/config.go | 62 +++- service/internal/credentials/credentials.go | 15 - service/kas/kas.go | 5 +- service/pkg/db/db.go | 8 +- service/pkg/db/db_migration.go | 10 +- service/pkg/server/options.go | 37 +++ service/pkg/server/services.go | 293 ++++++++++-------- service/pkg/server/services_test.go | 249 ++++++++++----- service/pkg/server/start.go | 152 +++++---- service/pkg/server/start_test.go | 18 +- .../pkg/serviceregistry/serviceregistry.go | 137 ++++++-- 21 files changed, 707 insertions(+), 373 deletions(-) delete mode 100644 service/internal/credentials/credentials.go diff --git a/docker-compose.yaml b/docker-compose.yaml index e9f371617..a1a15f9b8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -18,6 +18,7 @@ services: - "-Djavax.net.ssl.trustStore=/truststore/truststore.jks" - "--spi-truststore-file-hostname-verification-policy=ANY" environment: + KC_PROXY: edge KC_HTTP_RELATIVE_PATH: /auth KC_DB_VENDOR: postgres KC_DB_URL_HOST: keycloakdb @@ -33,7 +34,7 @@ services: KC_HTTPS_PORT: "8443" KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: changeme - KC_HOSTNAME_URL: http://localhost:8888/auth + #KC_HOSTNAME_URL: http://localhost:8888/auth KC_FEATURES: "preview,token-exchange" KC_HEALTH_ENABLED: "true" KC_HTTPS_KEY_STORE_PASSWORD: "password" diff --git a/go.work.sum b/go.work.sum index d6bc2122f..1e9ce0b39 100644 --- a/go.work.sum +++ b/go.work.sum @@ -372,6 +372,7 @@ github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfy github.com/Microsoft/hcsshim v0.9.3/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim v0.9.4/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/hcsshim v0.11.5/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3 h1:4FA+QBaydEHlwxg0lMN3rhwoDaQy6LKhVWR4qvq4BuA= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= @@ -516,6 +517,7 @@ github.com/cockroachdb/errors v1.2.4 h1:Lap807SXTH5tri2TivECb/4abUkMZC9zRoLarvcK github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/container-orchestrated-devices/container-device-interface v0.5.4/go.mod h1:DjE95rfPiiSmG7uVXtg0z6MnPm/Lx4wxKCIts0ZE0vg= github.com/container-orchestrated-devices/container-device-interface v0.6.1 h1:mz77uJoP8im/4Zins+mPqt677ZMaflhoGaYrRAl5jvA= github.com/container-orchestrated-devices/container-device-interface v0.6.1/go.mod h1:40T6oW59rFrL/ksiSs7q45GzjGlbvxnA4xaK6cyq+kA= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= @@ -578,8 +580,6 @@ github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EX github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= -github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= @@ -609,11 +609,13 @@ github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue github.com/containerd/imgcrypt v1.1.4/go.mod h1:LorQnPtzL/T0IyCeftcsMEO7AqxUDbdO8j/tSUpgxvo= github.com/containerd/imgcrypt v1.1.7 h1:WSf9o9EQ0KGHiUx2ESFZ+PKf4nxK9BcvV/nJDX8RkB4= github.com/containerd/imgcrypt v1.1.7/go.mod h1:FD8gqIcX5aTotCtOmjeCsi3A1dHmTZpnMISGKSczt4k= +github.com/containerd/imgcrypt v1.1.8/go.mod h1:x6QvFIkMyO2qGIY2zXc88ivEzcbgvLdWjoZyGqDap5U= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.6.0 h1:hdztxwL0gCS1CrCa9bvD1SoJiFN4jBuRQhplCvCPMj8= github.com/containerd/nri v0.6.0/go.mod h1:F7OZfO4QTPqw5r87aq+syZJwiVvRYLIlHZiZDBV1W3A= +github.com/containerd/nri v0.6.1/go.mod h1:7+sX3wNx+LR7RzhjnJiUkFDhn18P5Bg/0VnJ/uXpRJM= github.com/containerd/protobuild v0.3.0 h1:RIyEIu+D+iIha6E1PREBPAXspSMFaDVam81JlolZWpg= github.com/containerd/protobuild v0.3.0/go.mod h1:5mNMFKKAwCIAkFBPiOdtRx2KiQlyEJeMXnL5R1DsWu8= github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= @@ -628,6 +630,7 @@ github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Ev github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak= github.com/containerd/ttrpc v1.2.3 h1:4jlhbXIGvijRtNC8F/5CpuJZ7yKOBFGFOOXg1bkISz0= github.com/containerd/ttrpc v1.2.3/go.mod h1:ieWsXucbb8Mj9PH0rXCw1i8IunRbbAiDkpXkbfflWBM= +github.com/containerd/ttrpc v1.2.4/go.mod h1:ojvb8SJBSch0XkqNO0L0YX/5NxR3UnVk2LzFKBK0upc= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= @@ -662,6 +665,7 @@ github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B github.com/containers/ocicrypt v1.1.3/go.mod h1:xpdkbVAuaH3WzbEabUd5yDsl9SwJA5pABH85425Es2g= github.com/containers/ocicrypt v1.1.6 h1:uoG52u2e91RE4UqmBICZY8dNshgfvkdl3BW6jnxiFaI= github.com/containers/ocicrypt v1.1.6/go.mod h1:WgjxPWdTJMqYMjf3M6cuIFFA1/MpyyhIM99YInA+Rvc= +github.com/containers/ocicrypt v1.1.10/go.mod h1:YfzSSr06PTHQwSTUKqDSjish9BeW1E4HUmreluQcMd8= github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= @@ -821,6 +825,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.66.6/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -986,6 +991,7 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= @@ -1137,6 +1143,7 @@ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= @@ -1510,6 +1517,7 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 h1:lIOOHPEbXzO3vnmx2gok1Tfs31Q8GQqKLc8vVqyQq/I= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1875,6 +1883,7 @@ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= @@ -1996,6 +2005,7 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -2019,8 +2029,6 @@ golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -2104,6 +2112,7 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= @@ -2210,6 +2219,7 @@ google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:CCviP9RmpZ1mxVr8MUjCnSiY09IbAXZxhLE6EhHIdPU= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= @@ -2222,6 +2232,7 @@ google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUE google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= @@ -2241,6 +2252,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= @@ -2460,5 +2472,7 @@ sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= tags.cncf.io/container-device-interface v0.6.2 h1:dThE6dtp/93ZDGhqaED2Pu374SOeUkBfuvkLuiTdwzg= tags.cncf.io/container-device-interface v0.6.2/go.mod h1:Shusyhjs1A5Na/kqPVLL0KqnHQHuunol9LFeUNkuGVE= +tags.cncf.io/container-device-interface v0.7.2/go.mod h1:Xb1PvXv2BhfNb3tla4r9JL129ck1Lxv9KuU6eVOfKto= tags.cncf.io/container-device-interface/specs-go v0.6.0 h1:V+tJJN6dqu8Vym6p+Ru+K5mJ49WL6Aoc5SJFSY0RLsQ= tags.cncf.io/container-device-interface/specs-go v0.6.0/go.mod h1:hMAwAbMZyBLdmYqWgYcKH0F/yctNpV3P35f+/088A80= +tags.cncf.io/container-device-interface/specs-go v0.7.0/go.mod h1:hMAwAbMZyBLdmYqWgYcKH0F/yctNpV3P35f+/088A80= diff --git a/opentdf-dev.yaml b/opentdf-dev.yaml index febcafacc..1731c8cb8 100644 --- a/opentdf-dev.yaml +++ b/opentdf-dev.yaml @@ -10,7 +10,6 @@ logger: # password: changeme services: kas: - enabled: true keyring: - kid: e1 alg: ec:secp256r1 @@ -22,16 +21,12 @@ services: - kid: r1 alg: rsa:2048 legacy: true - policy: - enabled: true authorization: - enabled: true ersurl: http://localhost:8080/entityresolution/resolve clientid: tdf-authorization-svc clientsecret: secret tokenendpoint: http://localhost:8888/auth/realms/opentdf/protocol/openid-connect/token entityresolution: - enabled: true url: http://localhost:8888/auth clientid: 'tdf-entity-resolution' clientsecret: 'secret' diff --git a/opentdf-example-no-kas.yaml b/opentdf-example-no-kas.yaml index 412c4b113..7d1849a18 100644 --- a/opentdf-example-no-kas.yaml +++ b/opentdf-example-no-kas.yaml @@ -1,3 +1,4 @@ +mode: core logger: level: debug type: text @@ -8,11 +9,6 @@ logger: # port: 5432 # user: postgres # password: changeme -services: - kas: - enabled: false - policy: - enabled: true server: auth: enabled: false diff --git a/opentdf-example.yaml b/opentdf-example.yaml index a55707392..f73c23e2d 100644 --- a/opentdf-example.yaml +++ b/opentdf-example.yaml @@ -8,15 +8,12 @@ db: # port: 5432 # user: postgres # password: changeme +# mode: all services: kas: - enabled: true eccertid: e1 rsacertid: r1 - policy: - enabled: true entityresolution: - enabled: true url: http://keycloak:8888/auth clientid: "tdf-entity-resolution" clientsecret: "secret" @@ -27,7 +24,6 @@ services: email: true username: true authorization: - enabled: true ersurl: http://localhost:8080/entityresolution/resolve clientid: tdf-authorization-svc clientsecret: secret diff --git a/service/authorization/authorization.go b/service/authorization/authorization.go index f8e61977f..2d43bff53 100644 --- a/service/authorization/authorization.go +++ b/service/authorization/authorization.go @@ -90,8 +90,8 @@ func NewRegistration() serviceregistry.Registration { panic(fmt.Errorf("failed to set defaults for authorization service config: %w", err)) } - if err := mapstructure.Decode(srp.Config.ExtraProps, &authZCfg); err != nil { - panic(fmt.Errorf("invalid auth svc cfg [%v] %w", srp.Config.ExtraProps, err)) + if err := mapstructure.Decode(srp.Config, &authZCfg); err != nil { + panic(fmt.Errorf("invalid auth svc cfg [%v] %w", srp.Config, err)) } // Validate Config diff --git a/service/cmd/start.go b/service/cmd/start.go index 8373cac16..31d8c0e8d 100644 --- a/service/cmd/start.go +++ b/service/cmd/start.go @@ -12,6 +12,7 @@ func init() { RunE: start, } startCmd.SilenceUsage = true + rootCmd.AddCommand(&startCmd) } diff --git a/service/entityresolution/entityresolution.go b/service/entityresolution/entityresolution.go index cc9ea0e69..ce49d64b2 100644 --- a/service/entityresolution/entityresolution.go +++ b/service/entityresolution/entityresolution.go @@ -23,7 +23,7 @@ func NewRegistration() serviceregistry.Registration { ServiceDesc: &entityresolution.EntityResolutionService_ServiceDesc, RegisterFunc: func(srp serviceregistry.RegistrationParams) (any, serviceregistry.HandlerServer) { var inputIdpConfig keycloak.KeycloakConfig - confJSON, err := json.Marshal(srp.Config.ExtraProps) + confJSON, err := json.Marshal(srp.Config) if err != nil { panic(err) } diff --git a/service/go.mod b/service/go.mod index 78fd74030..e3fec2997 100644 --- a/service/go.mod +++ b/service/go.mod @@ -28,14 +28,15 @@ require ( github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.32.0 github.com/valyala/fasthttp v1.52.0 github.com/wI2L/jsondiff v0.5.2 + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/oauth2 v0.20.0 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v2 v2.4.0 - gotest.tools/v3 v3.5.0 + gotest.tools/v3 v3.5.1 ) require ( @@ -47,13 +48,14 @@ require ( github.com/casbin/govaluate v1.1.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/containerd/containerd v1.7.14 // indirect + github.com/containerd/containerd v1.7.18 // indirect + github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v26.1.4+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.0.3+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -67,14 +69,13 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect ) require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.1-20240508200655-46a4cf4ba109.1 // indirect dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect; indi\ + github.com/Microsoft/go-winio v0.6.2 // indirect; indi\ github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/logr v1.4.1 // indirect @@ -144,8 +145,6 @@ require ( go.opentelemetry.io/otel/sdk v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/service/go.sum b/service/go.sum index d82e287ca..f34170b76 100644 --- a/service/go.sum +++ b/service/go.sum @@ -12,8 +12,8 @@ github.com/ClickHouse/clickhouse-go/v2 v2.17.1 h1:ZCmAYWpu75IyEi7+Yrs/uaAjiCGY5w github.com/ClickHouse/clickhouse-go/v2 v2.17.1/go.mod h1:rkGTvFDTLqLIm0ma+13xmcCfr/08Gvs7KmFt1tgiWHQ= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.12.0 h1:rbICA+XZFwrBef2Odk++0LjFvClNCJGRK+fsrP254Ts= github.com/Microsoft/hcsshim v0.12.0/go.mod h1:RZV12pcHCXQ42XnlQ3pz6FZfmrC1C+R4gaOHhRNML1g= github.com/Nerzal/gocloak/v13 v13.9.0 h1:YWsJsdM5b0yhM2Ba3MLydiOlujkBry4TtdzfIzSVZhw= @@ -50,10 +50,12 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/containerd/containerd v1.7.14 h1:H/XLzbnGuenZEGK+v0RkwTdv2u1QFAruMe5N0GNPJwA= -github.com/containerd/containerd v1.7.14/go.mod h1:YMC9Qt5yzNqXx/fO4j/5yYVIHXSRrlB3H7sxkUTvspg= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= +github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= @@ -75,12 +77,12 @@ github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWa github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= -github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE= +github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -353,8 +355,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= -github.com/testcontainers/testcontainers-go v0.30.0 h1:jmn/XS22q4YRrcMwWg0pAwlClzs/abopbsBzrepyc4E= -github.com/testcontainers/testcontainers-go v0.30.0/go.mod h1:K+kHNGiM5zjklKjgTtcrEetF3uhWbMUyqAQoyoh8Pf0= +github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME= +github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -481,6 +483,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -523,8 +527,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= diff --git a/service/internal/config/config.go b/service/internal/config/config.go index eb45f7232..0eb776a99 100644 --- a/service/internal/config/config.go +++ b/service/internal/config/config.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/creasty/defaults" + "github.com/go-playground/validator/v10" "github.com/opentdf/platform/service/internal/server" "github.com/opentdf/platform/service/logger" "github.com/opentdf/platform/service/pkg/db" @@ -17,12 +18,46 @@ import ( "github.com/spf13/viper" ) +// Config represents the configuration settings for the service. type Config struct { - DevMode bool `mapstructure:"dev_mode"` - DB db.Config `yaml:"db"` - Server server.Config `yaml:"server"` - Logger logger.Config `yaml:"logger"` - Services map[string]serviceregistry.ServiceConfig `yaml:"services" default:"{\"policy\": {\"enabled\": true}, \"health\": {\"enabled\": true}, \"authorization\": {\"enabled\": true}, \"wellknown\": {\"enabled\": true}, \"kas\": {\"enabled\": true}, \"entityresolution\": {\"enabled\": true}}"` + // DevMode specifies whether the service is running in development mode. + DevMode bool `mapstructure:"dev_mode"` + + // DB represents the configuration settings for the database. + DB db.Config `mapstructure:"db"` + + // Server represents the configuration settings for the server. + Server server.Config `mapstructure:"server"` + + // Logger represents the configuration settings for the logger. + Logger logger.Config `mapstructure:"logger"` + + // Mode specifies which services to run. + // By default, it runs all services. + Mode []string `mapstructure:"mode" default:"[\"all\"]"` + + // SDKConfig represents the configuration settings for the SDK. + SDKConfig SDKConfig `mapstructure:"sdk_config"` + + // Services represents the configuration settings for the services. + Services map[string]serviceregistry.ServiceConfig `mapstructure:"services"` +} + +// SDKConfig represents the configuration for the SDK. +type SDKConfig struct { + // Endpoint is the URL of the Core Platform endpoint. + Endpoint string `mapstructure:"endpoint"` + + // Plaintext specifies whether the SDK should use plaintext communication. + Plaintext bool `mapstructure:"plaintext" default:"false" validate:"boolean"` + + // ClientID is the client ID used for client credentials grant. + // It is required together with ClientSecret. + ClientID string `mapstructure:"client_id" validate:"required_with=ClientSecret"` + + // ClientSecret is the client secret used for client credentials grant. + // It is required together with ClientID. + ClientSecret string `mapstructure:"client_secret" validate:"required_with=ClientID"` } type Error string @@ -38,18 +73,14 @@ const ( ) // LoadConfig Load config with viper. -func LoadConfig(key string, file string) (*Config, error) { +func LoadConfig(key, file string) (*Config, error) { config := &Config{} + homedir, err := os.UserHomeDir() if err != nil { return nil, errors.Join(err, ErrLoadingConfig) } - // uncommment to debug config loading, - // issue is the loglevel directive is in the config yaml - // t := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ - // Level: slog.LevelDebug, - // }) - // v := viper.NewWithOptions(viper.WithLogger(slog.New(t))) + v := viper.NewWithOptions(viper.WithLogger(slog.Default())) v.AddConfigPath(fmt.Sprintf("%s/."+key, homedir)) v.AddConfigPath("." + key) @@ -83,6 +114,13 @@ func LoadConfig(key string, file string) (*Config, error) { return nil, errors.Join(err, ErrUnmarshallingConfig) } + // Validate Config + validate := validator.New() + + if err := validate.Struct(config); err != nil { + return nil, errors.Join(err, ErrUnmarshallingConfig) + } + return config, nil } diff --git a/service/internal/credentials/credentials.go b/service/internal/credentials/credentials.go deleted file mode 100644 index 1c00837c4..000000000 --- a/service/internal/credentials/credentials.go +++ /dev/null @@ -1,15 +0,0 @@ -package credentials - -import "os" - -type Credential struct { - FromEnv string `yaml:"fromEnv,omitempty"` - FromK8sSecret string `yaml:"fromK8sSecret,omitempty"` -} - -func (c Credential) Get() string { - if c.FromEnv != "" { - return os.Getenv(c.FromEnv) - } - return "" -} diff --git a/service/kas/kas.go b/service/kas/kas.go index 76deb2d90..14d21d0ea 100644 --- a/service/kas/kas.go +++ b/service/kas/kas.go @@ -32,8 +32,8 @@ func NewRegistration() serviceregistry.Registration { } var kasCfg access.KASConfig - if err := mapstructure.Decode(srp.Config.ExtraProps, &kasCfg); err != nil { - panic(fmt.Errorf("invalid kas cfg [%v] %w", srp.Config.ExtraProps, err)) + if err := mapstructure.Decode(srp.Config, &kasCfg); err != nil { + panic(fmt.Errorf("invalid kas cfg [%v] %w", srp.Config, err)) } switch { @@ -70,7 +70,6 @@ func NewRegistration() serviceregistry.Registration { CryptoProvider: srp.OTDF.CryptoProvider, SDK: srp.SDK, Logger: srp.Logger, - Config: &srp.Config, KASConfig: kasCfg, } diff --git a/service/pkg/db/db.go b/service/pkg/db/db.go index fcaba742a..8f8fec070 100644 --- a/service/pkg/db/db.go +++ b/service/pkg/db/db.go @@ -91,10 +91,10 @@ Multiple pools, schemas, or migrations per service are not supported. Multiple d PostgreSQL instance or multiple PostgreSQL servers per platform instance are not supported. */ type Client struct { - Pgx PgxIface - Logger *logger.Logger - config Config - + Pgx PgxIface + Logger *logger.Logger + config Config + ranMigrations bool // This is the stdlib connection that is used for transactions SQLDB *sql.DB } diff --git a/service/pkg/db/db_migration.go b/service/pkg/db/db_migration.go index b1a07f4ab..3f197f150 100644 --- a/service/pkg/db/db_migration.go +++ b/service/pkg/db/db_migration.go @@ -86,7 +86,7 @@ func (c *Client) RunMigrations(ctx context.Context, migrations *embed.FS) (int, applied++ } } - + c.ranMigrations = true slog.Info("migration up complete", slog.Any("post-op version", version)) return applied, nil } @@ -123,3 +123,11 @@ func (c *Client) MigrationDown(ctx context.Context, migrations *embed.FS) error slog.Info("migration down complete ", slog.Any("post-op version", res.Source.Version)) return nil } + +func (c *Client) MigrationsEnabled() bool { + return c.config.RunMigrations +} + +func (c *Client) RanMigrations() bool { + return c.ranMigrations +} diff --git a/service/pkg/server/options.go b/service/pkg/server/options.go index 49850c4b4..75eed19ee 100644 --- a/service/pkg/server/options.go +++ b/service/pkg/server/options.go @@ -1,5 +1,9 @@ package server +import ( + "github.com/opentdf/platform/service/pkg/serviceregistry" +) + type StartOptions func(StartConfig) StartConfig type StartConfig struct { @@ -8,6 +12,8 @@ type StartConfig struct { WaitForShutdownSignal bool PublicRoutes []string authzDefaultPolicyExtension [][]string + extraCoreServices []serviceregistry.Registration + extraServices []serviceregistry.Registration } // Deprecated: Use WithConfigKey @@ -18,6 +24,7 @@ func WithConfigName(name string) StartOptions { } } +// WithConfigFile option sets the configuration file path. func WithConfigFile(file string) StartOptions { return func(c StartConfig) StartConfig { c.ConfigFile = file @@ -25,6 +32,7 @@ func WithConfigFile(file string) StartOptions { } } +// WithConfigKey option sets the viper configuration key(filename). func WithConfigKey(key string) StartOptions { return func(c StartConfig) StartConfig { c.ConfigKey = key @@ -32,6 +40,7 @@ func WithConfigKey(key string) StartOptions { } } +// WithWaitForShutdownSignal option allows the server to wait for a shutdown signal before exiting. func WithWaitForShutdownSignal() StartOptions { return func(c StartConfig) StartConfig { c.WaitForShutdownSignal = true @@ -39,6 +48,9 @@ func WithWaitForShutdownSignal() StartOptions { } } +// WithPublicRoutes option sets the public routes for the server. +// It allows bypassing the authorization middleware for the specified routes. +// *** This should be used with caution as it can expose sensitive data. *** func WithPublicRoutes(routes []string) StartOptions { return func(c StartConfig) StartConfig { c.PublicRoutes = routes @@ -46,9 +58,34 @@ func WithPublicRoutes(routes []string) StartOptions { } } +// WithAuthZDefaultPolicyExtension option allows for extending the default casbin poliy +// Example: +// +// opentdf.WithAuthZDefaultPolicyExtension([][]string{ +// {"p","role:org-admin", "pep*", "*","allow"), +// }), func WithAuthZDefaultPolicyExtension(policies [][]string) StartOptions { return func(c StartConfig) StartConfig { c.authzDefaultPolicyExtension = policies return c } } + +// WithCoreServices option adds additional core services to the platform +// It takes a variadic parameter of type serviceregistry.Registration, which represents the core services to be added. +func WithCoreServices(services ...serviceregistry.Registration) StartOptions { + return func(c StartConfig) StartConfig { + c.extraCoreServices = append(c.extraCoreServices, services...) + return c + } +} + +// WithServices option adds additional services to the platform. +// This will set the mode for these services to the namespace name. +// It takes a variadic parameter of type serviceregistry.Registration, which represents the services to be added. +func WithServices(services ...serviceregistry.Registration) StartOptions { + return func(c StartConfig) StartConfig { + c.extraServices = append(c.extraServices, services...) + return c + } +} diff --git a/service/pkg/server/services.go b/service/pkg/server/services.go index 9870c5639..3dc34d897 100644 --- a/service/pkg/server/services.go +++ b/service/pkg/server/services.go @@ -2,8 +2,11 @@ package server import ( "context" + "embed" "fmt" "log/slog" + "slices" + "strings" "github.com/opentdf/platform/sdk" "github.com/opentdf/platform/service/authorization" @@ -19,162 +22,198 @@ import ( wellknown "github.com/opentdf/platform/service/wellknownconfiguration" ) -func registerServices() error { - services := []serviceregistry.Registration{ +const ( + modeALL = "all" + modeCore = "core" + modeKAS = "kas" + modeEssential = "essential" + + serviceKAS = "kas" + servicePolicy = "policy" + serviceWellKnown = "wellknown" + serviceEntityResolution = "entityresolution" + serviceAuthorization = "authorization" +) + +// registerEssentialServices registers the essential services to the given service registry. +// It takes a serviceregistry.Registry as input and returns an error if registration fails. +func registerEssentialServices(reg serviceregistry.Registry) error { + essentialServices := []serviceregistry.Registration{ health.NewRegistration(), - authorization.NewRegistration(), - kas.NewRegistration(), - wellknown.NewRegistration(), - entityresolution.NewRegistration(), } - services = append(services, policy.NewRegistrations()...) - - // Register the services - for _, s := range services { - if err := serviceregistry.RegisterService(s); err != nil { + // Register the essential services + for _, s := range essentialServices { + if err := reg.RegisterService(s, modeEssential); err != nil { return err //nolint:wrapcheck // We are all friends here } } return nil } -func startServices(ctx context.Context, cfg config.Config, otdf *server.OpenTDFServer, client *sdk.SDK, logger *logger.Logger) (func(), []serviceregistry.Service, error) { - // CloseServices is a function that will close all registered services - closeServices := func() { - logger.Info("stopping services") - for ns, registers := range serviceregistry.RegisteredServices { - for _, r := range registers { - // Only report on started services - if !r.Started { - continue - } - logger.Info("stopping service", slog.String("namespace", ns), slog.String("service", r.ServiceDesc.ServiceName)) - if r.Close != nil { - r.Close() - } +// registerCoreServices registers the core services based on the provided mode. +// It returns the list of registered services and any error encountered during registration. +func registerCoreServices(reg serviceregistry.Registry, mode []string) ([]string, error) { + var ( + services []serviceregistry.Registration + registeredServices []string + ) + + for _, m := range mode { + switch m { + case "all": + registeredServices = append(registeredServices, []string{servicePolicy, serviceAuthorization, serviceKAS, serviceWellKnown, serviceEntityResolution}...) + services = append(services, []serviceregistry.Registration{ + authorization.NewRegistration(), + kas.NewRegistration(), + wellknown.NewRegistration(), + entityresolution.NewRegistration(), + }...) + services = append(services, policy.NewRegistrations()...) + case "core": + registeredServices = append(registeredServices, []string{servicePolicy, serviceAuthorization, serviceWellKnown}...) + services = append(services, []serviceregistry.Registration{ + entityresolution.NewRegistration(), + authorization.NewRegistration(), + wellknown.NewRegistration(), + }...) + services = append(services, policy.NewRegistrations()...) + case "kas": + // If the mode is "kas", register only the KAS service + registeredServices = append(registeredServices, serviceKAS) + if err := reg.RegisterService(kas.NewRegistration(), modeKAS); err != nil { + return nil, err //nolint:wrapcheck // We are all friends here } + default: + continue } } - services := []serviceregistry.Service{} + // Register the services + for _, s := range services { + if err := reg.RegisterCoreService(s); err != nil { + return nil, err //nolint:wrapcheck // We are all friends here + } + } + + return registeredServices, nil +} +// startServices iterates through the registered namespaces and starts the services +// based on the configuration and namespace mode. It creates a new service logger +// and a database client if required. It registers the services with the gRPC server, +// in-process gRPC server, and gRPC gateway. Finally, it logs the status of each service. +func startServices(ctx context.Context, cfg config.Config, otdf *server.OpenTDFServer, client *sdk.SDK, logger *logger.Logger, reg serviceregistry.Registry) error { // Iterate through the registered namespaces - for ns, registers := range serviceregistry.RegisteredServices { - // Check if the service is enabled - if !cfg.Services[ns].Enabled { - logger.Debug("start service skipped", slog.String("namespace", ns)) + for ns, namespace := range reg { + // modeEnabled checks if the mode is enabled based on the configuration and namespace mode. + // It returns true if the mode is "all" or "essential" in the configuration, or if it matches the namespace mode. + modeEnabled := slices.ContainsFunc(cfg.Mode, func(m string) bool { + if strings.EqualFold(m, modeALL) || strings.EqualFold(namespace.Mode, modeEssential) { + return true + } + return strings.EqualFold(m, namespace.Mode) + }) + + // Skip the namespace if the mode is not enabled + if !modeEnabled { + logger.Info("skipping namespace", slog.String("namespace", ns), slog.String("mode", namespace.Mode)) continue } - // Use a single database client per namespace and run migrations once per namespace - var d *db.Client - runMigrations := cfg.DB.RunMigrations + svcLogger := logger.With("namespace", ns) + + var svcDBClient *db.Client - for _, r := range registers { - s, db, err := startService(ctx, &cfg, r, otdf, client, d, &runMigrations, logger) + // Create new service logger + for _, svc := range namespace.Services { + // Get new db client if it is required and not already created + if svc.DB.Required && svcDBClient == nil { + logger.Debug("creating database client", slog.String("namespace", ns)) + var err error + svcDBClient, err = newServiceDBClient(ctx, cfg.Logger, cfg.DB, ns, svc.DB.Migrations) + if err != nil { + return err + } + } + + err := svc.Start(ctx, serviceregistry.RegistrationParams{ + Config: cfg.Services[svc.Namespace], + Logger: svcLogger, + DBClient: svcDBClient, + SDK: client, + WellKnownConfig: wellknown.RegisterConfiguration, + RegisterReadinessCheck: health.RegisterReadinessCheck, + OTDF: otdf, // TODO: REMOVE THIS + }) if err != nil { - return closeServices, services, err + return err + } + // Register the service with the gRPC server + if err := svc.RegisterGRPCServer(otdf.GRPCServer); err != nil { + return err } - d = db - services = append(services, s) - } - } - return closeServices, services, nil -} + // Register the service with in process gRPC server + if err := svc.RegisterGRPCServer(otdf.GRPCInProcess.GetGrpcServer()); err != nil { + return err + } -func startService( - ctx context.Context, - cfg *config.Config, - s serviceregistry.Service, - otdf *server.OpenTDFServer, - client *sdk.SDK, - d *db.Client, - runMigrations *bool, - logger *logger.Logger, -) (serviceregistry.Service, *db.Client, error) { - // Create the database client only if required - if s.DB.Required && d == nil { - var err error - - logger.Info("creating database client", slog.String("namespace", s.Namespace)) - d, err = db.New(ctx, cfg.DB, cfg.Logger, - db.WithService(s.Namespace), - db.WithMigrations(s.DB.Migrations), - ) - if err != nil { - return s, d, fmt.Errorf("issue creating database client for %s: %w", s.Namespace, err) - } - } + // Register the service with the gRPC gateway + if err := svc.RegisterHTTPServer(ctx, otdf.Mux); err != nil { //nolint:staticcheck // This is deprecated for internal tracking + logger.Error("failed to register service to grpc gateway", slog.String("namespace", ns), slog.String("error", err.Error())) + return err + } - // Run migrations IFF a service requires it and they're configured to run but haven't run yet - shouldRun := s.DB.Required && *runMigrations - if shouldRun { - logger.Info("running database migrations") - appliedMigrations, err := d.RunMigrations(ctx, s.DB.Migrations) - if err != nil { - return s, d, fmt.Errorf("issue running database migrations: %w", err) + logger.Info( + "service running", + slog.String("namespace", ns), + slog.String("service", svc.ServiceDesc.ServiceName), + slog.Group("database", + slog.Any("required", svcDBClient != nil), + slog.Any("migrationStatus", determineStatusOfMigration(svcDBClient)), + ), + ) } - logger.Info("database migrations complete", - slog.Int("applied", appliedMigrations), - ) - // Only run migrations once - *runMigrations = false } - if !shouldRun { - requiredAlreadyRan := s.DB.Required && cfg.DB.RunMigrations && !*runMigrations - noDBRequired := !s.DB.Required - migrationsDisabled := !cfg.DB.RunMigrations - - reason := "undetermined" - if requiredAlreadyRan { //nolint:gocritic // This is more readable than a switch - reason = "required migrations already ran" - } else if noDBRequired { - reason = "service does not require a database" - } else if migrationsDisabled { - reason = "migrations are disabled" - } + return nil +} - logger.Info("skipping migrations", - slog.String("namespace", s.Namespace), - slog.String("service", s.ServiceDesc.ServiceName), - slog.Bool("configured runMigrations", cfg.DB.RunMigrations), - slog.String("reason", reason), - ) +// newServiceDBClient creates a new database client for the specified namespace. +// It initializes the client with the provided context, logger configuration, database configuration, +// namespace, and migrations. It returns the created client and any error encountered during creation. +func newServiceDBClient(ctx context.Context, logCfg logger.Config, dbCfg db.Config, ns string, migrations *embed.FS) (*db.Client, error) { + var err error + + client, err := db.New(ctx, dbCfg, logCfg, + db.WithService(ns), + db.WithMigrations(migrations), + ) + if err != nil { + return nil, fmt.Errorf("issue creating database client for %s: %w", ns, err) } - // Create the service - impl, handler := s.RegisterFunc(serviceregistry.RegistrationParams{ - Config: cfg.Services[s.Namespace], - OTDF: otdf, - DBClient: d, - SDK: client, - WellKnownConfig: wellknown.RegisterConfiguration, - RegisterReadinessCheck: health.RegisterReadinessCheck, - Logger: logger.With("namespace", s.Namespace), - }) - - // Register the service with the gRPC server - otdf.GRPCServer.RegisterService(s.ServiceDesc, impl) - - // Register the service with in process gRPC server - otdf.GRPCInProcess.GetGrpcServer().RegisterService(s.ServiceDesc, impl) - - // Register the service with the gRPC gateway - if err := handler(ctx, otdf.Mux, impl); err != nil { - logger.Error("failed to start service", slog.String("namespace", s.Namespace), slog.String("error", err.Error())) - return s, d, err - } + return client, nil +} - logger.Info("started service", slog.String("namespace", s.Namespace), slog.String("service", s.ServiceDesc.ServiceName)) - s.Started = true - s.Close = func() { - if d != nil { - logger.Info("closing database client", slog.String("namespace", s.Namespace), slog.String("service", s.ServiceDesc.ServiceName)) - // TODO: this might be a problem if we can't call close on the db client multiple times - d.Close() - } +// determineStatusOfMigration determines the status of the migration based on the provided client. +// It checks if the client is required, if the required migrations have already been ran, +// if the service does not require a database, or if the migrations are disabled. +// It returns a string indicating the reason for the determined status. +func determineStatusOfMigration(client *db.Client) string { + required := (client != nil) + requiredAlreadyRan := required && client.MigrationsEnabled() && client.RanMigrations() + noDBRequired := !required + migrationsDisabled := (required && !client.MigrationsEnabled()) + + reason := "undetermined" + switch { + case requiredAlreadyRan: + reason = "required migrations already ran" + case noDBRequired: + reason = "service does not require a database" + case migrationsDisabled: + reason = "migrations are disabled" } - return s, d, nil + return reason } diff --git a/service/pkg/server/services_test.go b/service/pkg/server/services_test.go index 7f493b7a8..ee50ab6fc 100644 --- a/service/pkg/server/services_test.go +++ b/service/pkg/server/services_test.go @@ -7,10 +7,7 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/opentdf/platform/service/internal/config" "github.com/opentdf/platform/service/logger" - "github.com/opentdf/platform/service/pkg/db" "github.com/opentdf/platform/service/pkg/serviceregistry" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "google.golang.org/grpc" ) @@ -29,7 +26,7 @@ type mockTestServiceOptions struct { dbRegister serviceregistry.DBRegister } -func mockTestServiceRegistry(opts mockTestServiceOptions) (func() error, *spyTestService) { +func mockTestServiceRegistry(opts mockTestServiceOptions) (serviceregistry.Registration, *spyTestService) { spy := &spyTestService{} mockTestServiceDefaults := mockTestServiceOptions{ namespace: "test", @@ -55,23 +52,21 @@ func mockTestServiceRegistry(opts mockTestServiceOptions) (func() error, *spyTes serviceHandler = opts.serviceHandler } - return func() error { - return serviceregistry.RegisterService(serviceregistry.Registration{ - Namespace: namespace, - ServiceDesc: &grpc.ServiceDesc{ - ServiceName: serviceName, - HandlerType: serviceHandlerType, - }, - RegisterFunc: func(srp serviceregistry.RegistrationParams) (any, serviceregistry.HandlerServer) { - return opts.serviceObject, func(ctx context.Context, mux *runtime.ServeMux, server any) error { - spy.wasCalled = true - spy.callParams = append(spy.callParams, srp, ctx, mux, server) - return serviceHandler(ctx, mux, server) - } - }, - - DB: opts.dbRegister, - }) + return serviceregistry.Registration{ + Namespace: namespace, + ServiceDesc: &grpc.ServiceDesc{ + ServiceName: serviceName, + HandlerType: serviceHandlerType, + }, + RegisterFunc: func(srp serviceregistry.RegistrationParams) (any, serviceregistry.HandlerServer) { + return opts.serviceObject, func(ctx context.Context, mux *runtime.ServeMux, server any) error { + spy.wasCalled = true + spy.callParams = append(spy.callParams, srp, ctx, mux, server) + return serviceHandler(ctx, mux, server) + } + }, + + DB: opts.dbRegister, }, spy } @@ -80,93 +75,189 @@ type ServiceTestSuite struct { } func TestServiceTestSuite(t *testing.T) { - suite.Run(t, new(StartTestSuite)) + suite.Run(t, new(ServiceTestSuite)) } -func (suite *ServiceTestSuite) BeforeTest(_, _ string) { - serviceregistry.RegisteredServices = make(serviceregistry.NamespaceMap) +func (suite *ServiceTestSuite) TestRegisterEssentialServiceRegistrationIsSuccessful() { + registry := serviceregistry.NewServiceRegistry() + err := registerEssentialServices(registry) + suite.Require().NoError(err) + ns, err := registry.GetNamespace("health") + suite.Require().NoError(err) + suite.Len(ns.Services, 1) + suite.Equal(modeEssential, ns.Mode) } -func (suite *ServiceTestSuite) TestRegisterServicesIsSuccessful() { - t := suite.T() - err := registerServices() - require.NoError(t, err) +func (suite *ServiceTestSuite) Test_RegisterCoreServices_In_Mode_ALL_Expect_All_Services_Registered() { + registry := serviceregistry.NewServiceRegistry() + _, err := registerCoreServices(registry, []string{modeALL}) + suite.Require().NoError(err) + + authz, err := registry.GetNamespace(serviceAuthorization) + suite.Require().NoError(err) + suite.Len(authz.Services, 1) + suite.Equal(modeCore, authz.Mode) + + kas, err := registry.GetNamespace(serviceKAS) + suite.Require().NoError(err) + suite.Len(kas.Services, 1) + suite.Equal(modeCore, kas.Mode) + + policy, err := registry.GetNamespace(servicePolicy) + suite.Require().NoError(err) + suite.Len(policy.Services, 6) + suite.Equal(modeCore, policy.Mode) + + wellKnown, err := registry.GetNamespace(serviceWellKnown) + suite.Require().NoError(err) + suite.Len(wellKnown.Services, 1) + suite.Equal(modeCore, wellKnown.Mode) + + ers, err := registry.GetNamespace(serviceEntityResolution) + suite.Require().NoError(err) + suite.Len(ers.Services, 1) + suite.Equal(modeCore, ers.Mode) +} + +// Every service except kas is registered +func (suite *ServiceTestSuite) Test_RegisterCoreServices_In_Mode_Core_Expect_Core_Services_Registered() { + registry := serviceregistry.NewServiceRegistry() + _, err := registerCoreServices(registry, []string{modeCore}) + suite.Require().NoError(err) + + authz, err := registry.GetNamespace(serviceAuthorization) + suite.Require().NoError(err) + suite.Len(authz.Services, 1) + suite.Equal(modeCore, authz.Mode) + + _, err = registry.GetNamespace(serviceKAS) + suite.Require().Error(err) + suite.Require().ErrorContains(err, "namespace not found") + + policy, err := registry.GetNamespace(servicePolicy) + suite.Require().NoError(err) + suite.Len(policy.Services, 6) + suite.Equal(modeCore, policy.Mode) + + wellKnown, err := registry.GetNamespace(serviceWellKnown) + suite.Require().NoError(err) + suite.Len(wellKnown.Services, 1) + suite.Equal(modeCore, wellKnown.Mode) + + ers, err := registry.GetNamespace(serviceEntityResolution) + suite.Require().NoError(err) + suite.Len(ers.Services, 1) + suite.Equal(modeCore, ers.Mode) +} + +// Register core and kas services +func (suite *ServiceTestSuite) Test_RegisterServices_In_Mode_Core_Plus_Kas_Expect_Core_And_Kas_Services_Registered() { + registry := serviceregistry.NewServiceRegistry() + _, err := registerCoreServices(registry, []string{modeCore, modeKAS}) + suite.Require().NoError(err) + + authz, err := registry.GetNamespace(serviceAuthorization) + suite.Require().NoError(err) + suite.Len(authz.Services, 1) + suite.Equal(modeCore, authz.Mode) + + kas, err := registry.GetNamespace(serviceKAS) + suite.Require().NoError(err) + suite.Len(kas.Services, 1) + suite.Equal(modeKAS, kas.Mode) + + policy, err := registry.GetNamespace(servicePolicy) + suite.Require().NoError(err) + suite.Len(policy.Services, 6) + suite.Equal(modeCore, policy.Mode) + + wellKnown, err := registry.GetNamespace(serviceWellKnown) + suite.Require().NoError(err) + suite.Len(wellKnown.Services, 1) + suite.Equal(modeCore, wellKnown.Mode) + + ers, err := registry.GetNamespace(serviceEntityResolution) + suite.Require().NoError(err) + suite.Len(ers.Services, 1) + suite.Equal(modeCore, ers.Mode) } func (suite *ServiceTestSuite) TestStartServicesWithVariousCases() { - t := suite.T() ctx := context.Background() + registry := serviceregistry.NewServiceRegistry() + // Test service which will be enabled - registerTest, testSpy := mockTestServiceRegistry(mockTestServiceOptions{}) - err := registerTest() - require.NoError(t, err) + registerTest, testSpy := mockTestServiceRegistry(mockTestServiceOptions{ + serviceObject: &TestService{}, + }) + err := registry.RegisterService(registerTest, "test") + suite.Require().NoError(err) + /* + Configuring test with db now tries to connect to database. + */ // Test service with DB which will be enabled - registerTestWithDB, testWithDBSpy := mockTestServiceRegistry(mockTestServiceOptions{ - namespace: "test_with_db", - serviceName: "TestWithDBService", - dbRegister: serviceregistry.DBRegister{ - Required: true, - }, - }) - err = registerTestWithDB() - require.NoError(t, err) + // registerTestWithDB, testWithDBSpy := mockTestServiceRegistry(mockTestServiceOptions{ + // namespace: "test_with_db", + // serviceName: "TestWithDBService", + // dbRegister: serviceregistry.DBRegister{ + // Required: true, + // }, + // }) + // err = registry.RegisterService(registerTestWithDB, "test_with_db") + // require.NoError(t, err) // FooBar service which won't be enabled registerFoobar, foobarSpy := mockTestServiceRegistry(mockTestServiceOptions{ namespace: "foobar", serviceName: "FooBarService", }) - err = registerFoobar() - require.NoError(t, err) + err = registry.RegisterService(registerFoobar, "foobar") + suite.Require().NoError(err) otdf, err := mockOpenTDFServer() - require.NoError(t, err) - - logger, err := logger.NewLogger(logger.Config{Output: "stdout", Level: "info", Type: "json"}) - require.NoError(t, err) - - cF, services, err := startServices(ctx, config.Config{ - DB: db.Config{ - Host: "localhost", - Port: 5432, - Database: "opentdf", - User: "", - Password: "", - RunMigrations: false, - }, + suite.Require().NoError(err) + + newLogger, err := logger.NewLogger(logger.Config{Output: "stdout", Level: "info", Type: "json"}) + suite.Require().NoError(err) + + err = startServices(ctx, config.Config{ + Mode: []string{"test"}, + Logger: logger.Config{Output: "stdout", Level: "info", Type: "json"}, + // DB: db.Config{ + // Host: "localhost", + // Port: 5432, + // Database: "opentdf", + // User: "", + // Password: "", + // RunMigrations: false, + // }, Services: map[string]serviceregistry.ServiceConfig{ - "test": { - Enabled: true, - }, - "test_with_db": { - Enabled: true, - }, - "foobar": { - Enabled: false, - }, + "test": {}, + "test_with_db": {}, + "foobar": {}, }, - }, otdf, nil, logger) - require.NoError(t, err) - require.NotNil(t, cF) - assert.Lenf(t, services, 2, "expected 2 services enabled, got %d", len(services)) + }, otdf, nil, newLogger, registry) + suite.Require().NoError(err) + // require.NotNil(t, cF) + // assert.Lenf(t, services, 2, "expected 2 services enabled, got %d", len(services)) // Expecting a test service with no DBClient - assert.True(t, testSpy.wasCalled) + suite.True(testSpy.wasCalled) regParams, ok := testSpy.callParams[0].(serviceregistry.RegistrationParams) - require.True(t, ok) - assert.Nil(t, regParams.DBClient) + suite.Require().True(ok) + suite.Nil(regParams.DBClient) // Expecting a test service with a DBClient - assert.True(t, testWithDBSpy.wasCalled) - regParams, ok = testWithDBSpy.callParams[0].(serviceregistry.RegistrationParams) - require.True(t, ok) - assert.NotNil(t, regParams.DBClient) + // assert.True(t, testWithDBSpy.wasCalled) + // regParams, ok = testWithDBSpy.callParams[0].(serviceregistry.RegistrationParams) + // require.True(t, ok) + // assert.NotNil(t, regParams.DBClient) // Not expecting a foobar service since it's not enabled - assert.False(t, foobarSpy.wasCalled) + suite.False(foobarSpy.wasCalled) // call close function - cF() + registry.Shutdown() } diff --git a/service/pkg/server/start.go b/service/pkg/server/start.go index 7314908dd..da7e971e7 100644 --- a/service/pkg/server/start.go +++ b/service/pkg/server/start.go @@ -13,7 +13,9 @@ import ( "github.com/opentdf/platform/service/internal/config" "github.com/opentdf/platform/service/internal/server" "github.com/opentdf/platform/service/logger" + "github.com/opentdf/platform/service/pkg/serviceregistry" wellknown "github.com/opentdf/platform/service/wellknownconfiguration" + "golang.org/x/exp/slices" ) const devModeMessage = ` @@ -33,25 +35,18 @@ func Start(f ...StartOptions) error { ctx := context.Background() - slog.Info("starting opentdf services") - slog.Debug("loading configuration") - conf, err := config.LoadConfig(startConfig.ConfigKey, startConfig.ConfigFile) + cfg, err := config.LoadConfig(startConfig.ConfigKey, startConfig.ConfigFile) if err != nil { return fmt.Errorf("could not load config: %w", err) } - if conf.DevMode { + if cfg.DevMode { fmt.Print(devModeMessage) //nolint:forbidigo // This ascii art is only displayed in dev mode } - // Set allowed public routes when platform is being extended - if len(startConfig.PublicRoutes) > 0 { - conf.Server.Auth.PublicRoutes = startConfig.PublicRoutes - } - - slog.Debug("starting logger") - logger, err := logger.NewLogger(conf.Logger) + slog.Debug("configuring logger") + logger, err := logger.NewLogger(cfg.Logger) if err != nil { return fmt.Errorf("could not start logger: %w", err) } @@ -59,15 +54,20 @@ func Start(f ...StartOptions) error { // Set default for places we can't pass the logger slog.SetDefault(logger.Logger) - logger.Debug("config loaded", slog.Any("config", conf)) + logger.Info("starting opentdf services") + + // Set allowed public routes when platform is being extended + if len(startConfig.PublicRoutes) > 0 { + logger.Info("additional public routes added", slog.Any("routes", startConfig.PublicRoutes)) + cfg.Server.Auth.PublicRoutes = startConfig.PublicRoutes + } - // Required services - conf.Server.WellKnownConfigRegister = wellknown.RegisterConfiguration + logger.Debug("config loaded", slog.Any("config", cfg)) // Create new server for grpc & http. Also will support in process grpc potentially too - logger.Debug("init opentdf server") - conf.Server.WellKnownConfigRegister = wellknown.RegisterConfiguration - otdf, err := server.NewOpenTDFServer(conf.Server, logger) + logger.Debug("initializing opentdf server") + cfg.Server.WellKnownConfigRegister = wellknown.RegisterConfiguration + otdf, err := server.NewOpenTDFServer(cfg.Server, logger) if err != nil { logger.Error("issue creating opentdf server", slog.String("error", err.Error())) return fmt.Errorf("issue creating opentdf server: %w", err) @@ -88,60 +88,108 @@ func Start(f ...StartOptions) error { } } + // Initialize the service registry + logger.Debug("initializing service registry") + svcRegistry := serviceregistry.NewServiceRegistry() + defer svcRegistry.Shutdown() + + // Register essential services every service needs (e.g. health check) + logger.Debug("registering essential services") + if err := registerEssentialServices(svcRegistry); err != nil { + logger.Error("could not register essential services", slog.String("error", err.Error())) + return fmt.Errorf("could not register essential services: %w", err) + } + logger.Debug("registering services") - if err := registerServices(); err != nil { - logger.Error("issue registering services", slog.String("error", err.Error())) - return fmt.Errorf("issue registering services: %w", err) - } - - // Create the SDK client for services to use - var sdkOptions []sdk.Option - for name, service := range conf.Services { - if service.Remote.Endpoint == "" && service.Enabled { - switch name { - case "policy": - sdkOptions = append(sdkOptions, sdk.WithCustomPolicyConnection(otdf.GRPCInProcess.Conn())) - case "authorization": - sdkOptions = append(sdkOptions, sdk.WithCustomAuthorizationConnection(otdf.GRPCInProcess.Conn())) - case "entityresolution": - sdkOptions = append(sdkOptions, sdk.WithCustomEntityResolutionConnection(otdf.GRPCInProcess.Conn())) + + var registeredCoreServices []string + + registeredCoreServices, err = registerCoreServices(svcRegistry, cfg.Mode) + if err != nil { + logger.Error("could not register core services", slog.String("error", err.Error())) + return fmt.Errorf("could not register core services: %w", err) + } + + // Register Extra Core Services + if len(startConfig.extraCoreServices) > 0 { + logger.Debug("registering extra core services") + for _, service := range startConfig.extraCoreServices { + err := svcRegistry.RegisterCoreService(service) + if err != nil { + logger.Error("could not register extra core service", slog.String("error", err.Error())) + return fmt.Errorf("could not register extra core service: %w", err) + } + } + } + + // Register extra services + if len(startConfig.extraServices) > 0 { + logger.Debug("registering extra services") + for _, service := range startConfig.extraServices { + err := svcRegistry.RegisterService(service, service.Namespace) + if err != nil { + logger.Error("could not register extra service", slog.String("namespace", service.Namespace), slog.String("error", err.Error())) + return fmt.Errorf("could not register extra service: %w", err) } } } - // Use IPC for the SDK client - sdkOptions = append(sdkOptions, sdk.WithIPC()) + logger.Info("registered the following core services", slog.Any("core_services", registeredCoreServices)) - client, err := sdk.New("", sdkOptions...) - if err != nil { - logger.Error("issue creating sdk client", slog.String("error", err.Error())) - return fmt.Errorf("issue creating sdk client: %w", err) + var ( + sdkOptions []sdk.Option + client *sdk.SDK + ) + + // If the mode is not all or core, we need to have a valid SDK config + if !slices.Contains(cfg.Mode, "all") && !slices.Contains(cfg.Mode, "core") && cfg.SDKConfig == (config.SDKConfig{}) { + logger.Error("mode is not all or core, but no sdk config provided") + return errors.New("mode is not all or core, but no sdk config provided") + } + + // If client credentials are provided, use them + if cfg.SDKConfig.ClientID != "" && cfg.SDKConfig.ClientSecret != "" { + sdkOptions = append(sdkOptions, sdk.WithClientCredentials(cfg.SDKConfig.ClientID, cfg.SDKConfig.ClientSecret, nil)) + } + + // If the mode is all, use IPC for the SDK client + if slices.Contains(cfg.Mode, "all") || slices.Contains(cfg.Mode, "core") { + // Use IPC for the SDK client + sdkOptions = append(sdkOptions, sdk.WithIPC()) + sdkOptions = append(sdkOptions, sdk.WithCustomPolicyConnection(otdf.GRPCInProcess.Conn())) + sdkOptions = append(sdkOptions, sdk.WithCustomAuthorizationConnection(otdf.GRPCInProcess.Conn())) + sdkOptions = append(sdkOptions, sdk.WithCustomEntityResolutionConnection(otdf.GRPCInProcess.Conn())) + + client, err = sdk.New("", sdkOptions...) + if err != nil { + logger.Error("issue creating sdk client", slog.String("error", err.Error())) + return fmt.Errorf("issue creating sdk client: %w", err) + } + } else { + // Use the provided SDK config + if cfg.SDKConfig.Plaintext { + sdkOptions = append(sdkOptions, sdk.WithInsecurePlaintextConn()) + } + client, err = sdk.New(cfg.SDKConfig.Endpoint, sdkOptions...) + if err != nil { + logger.Error("issue creating sdk client", slog.String("error", err.Error())) + return fmt.Errorf("issue creating sdk client: %w", err) + } } + defer client.Close() logger.Info("starting services") - closeServices, services, err := startServices(ctx, *conf, otdf, client, logger) + err = startServices(ctx, *cfg, otdf, client, logger, svcRegistry) if err != nil { logger.Error("issue starting services", slog.String("error", err.Error())) return fmt.Errorf("issue starting services: %w", err) } - defer closeServices() // Start the server logger.Info("starting opentdf") otdf.Start() - // Print out the registered services - logger.Info("services running") - for _, service := range services { - logger.Info( - "service running", - slog.String("namespace", service.Registration.Namespace), - slog.String("service", service.ServiceDesc.ServiceName), - slog.Bool("database", service.Registration.DB.Required), - ) - } - if startConfig.WaitForShutdownSignal { waitForShutdownSignal() } diff --git a/service/pkg/server/start_test.go b/service/pkg/server/start_test.go index fb0e3b8b5..2d0eba028 100644 --- a/service/pkg/server/start_test.go +++ b/service/pkg/server/start_test.go @@ -90,10 +90,6 @@ func TestStartTestSuite(t *testing.T) { suite.Run(t, new(StartTestSuite)) } -func (suite *StartTestSuite) BeforeTest(_, _ string) { - serviceregistry.RegisteredServices = make(serviceregistry.NamespaceMap) -} - func (suite *StartTestSuite) Test_Start_When_Extra_Service_Registered_Expect_Response() { t := suite.T() s, err := mockOpenTDFServer() @@ -113,17 +109,17 @@ func (suite *StartTestSuite) Test_Start_When_Extra_Service_Registered_Expect_Res return mux.HandlePath(http.MethodGet, "/healthz", t.TestHandler) }, }) - err = registerTestService() - require.NoError(t, err) + registry := serviceregistry.NewServiceRegistry() + err = registry.RegisterService(registerTestService, "test") + suite.Require().NoError(err) // Start services with test service - _, _, err = startServices(context.Background(), config.Config{ + err = startServices(context.Background(), config.Config{ + Mode: []string{"all"}, Services: map[string]serviceregistry.ServiceConfig{ - "test": { - Enabled: true, - }, + "test": {}, }, - }, s, nil, logger) + }, s, nil, logger, registry) require.NoError(t, err) s.Start() diff --git a/service/pkg/serviceregistry/serviceregistry.go b/service/pkg/serviceregistry/serviceregistry.go index 9cdd694c9..a5c1f7157 100644 --- a/service/pkg/serviceregistry/serviceregistry.go +++ b/service/pkg/serviceregistry/serviceregistry.go @@ -5,6 +5,7 @@ import ( "embed" "fmt" "log/slog" + "slices" "github.com/opentdf/platform/sdk" @@ -15,17 +16,7 @@ import ( "google.golang.org/grpc" ) -// ServiceConfig is a struct that holds the configuration for a service and used for the global -// config rollup powered by Viper (https://github.com/spf13/viper) -type ServiceConfig struct { - Enabled bool `yaml:"enabled"` - Remote RemoteServiceConfig `yaml:"remote"` - ExtraProps map[string]interface{} `json:"-" mapstructure:",remain"` -} - -type RemoteServiceConfig struct { - Endpoint string `yaml:"endpoint"` -} +type ServiceConfig map[string]any // RegistrationParams is a struct that holds the parameters needed to register a service // with the service registry. These parameters are passed to the RegisterFunc function defined @@ -88,35 +79,131 @@ type DBRegister struct { // of the service within the instance of the platform. type Service struct { Registration + impl any + handleFunc HandlerServer // Started is a flag that indicates whether the service has been started Started bool // Close is a function that can be called to close the service Close func() } -type ServiceMap map[string]Service -type NamespaceMap map[string]ServiceMap +// Start starts the service and performs necessary initialization steps. +// It returns an error if the service is already started or if there is an issue running database migrations. +func (s *Service) Start(ctx context.Context, params RegistrationParams) error { + if s.Started { + return fmt.Errorf("service already started") + } + + if s.DB.Required && !params.DBClient.RanMigrations() && params.DBClient.MigrationsEnabled() { + appliedMigrations, err := params.DBClient.RunMigrations(ctx, s.DB.Migrations) + if err != nil { + return fmt.Errorf("issue running database migrations: %w", err) + } + params.Logger.Info("database migrations complete", + slog.Int("applied", appliedMigrations), + ) + } -// RegisteredServices is a map of namespaces to services -// TODO remove the global variable and move towards a more functional approach -var RegisteredServices NamespaceMap + s.impl, s.handleFunc = s.RegisterFunc(params) -// RegisterService is a function that registers a service with the service registry. -func RegisterService(r Registration) error { - if RegisteredServices == nil { - RegisteredServices = make(NamespaceMap, 0) + s.Started = true + return nil +} + +// RegisterGRPCServer registers the gRPC server with the service implementation. +// It checks if the service implementation is registered and then registers the service with the server. +// It returns an error if the service implementation is not registered. +func (s *Service) RegisterGRPCServer(server *grpc.Server) error { + if s.impl == nil { + return fmt.Errorf("service did not register an implementation") } - if RegisteredServices[r.Namespace] == nil { - RegisteredServices[r.Namespace] = make(ServiceMap, 0) + server.RegisterService(s.ServiceDesc, s.impl) + return nil +} + +// Deprecated: RegisterHTTPServer is deprecated and should not be used going forward. +// We will be looking onto other alternatives like bufconnect to replace this. +// RegisterHTTPServer registers an HTTP server with the service. +// It takes a context, a ServeMux, and an implementation function as parameters. +// If the service did not register a handler, it returns an error. +func (s *Service) RegisterHTTPServer(ctx context.Context, mux *runtime.ServeMux) error { + if s.handleFunc == nil { + return fmt.Errorf("service did not register a handler") + } + return s.handleFunc(ctx, mux, s.impl) +} + +// namespace represents a namespace in the service registry. +type Namespace struct { + Mode string + Services []Service +} + +// Registry represents a map of service namespaces. +type Registry map[string]Namespace + +// NewServiceRegistry creates a new instance of the service registry. +func NewServiceRegistry() Registry { + return make(Registry) +} + +// RegisterCoreService registers a core service with the given registration information. +// It calls the RegisterService method of the Registry instance with the provided registration and service type "core". +// Returns an error if the registration fails. +func (reg Registry) RegisterCoreService(r Registration) error { + return reg.RegisterService(r, "core") +} + +// RegisterService registers a service in the service registry. +// It takes a Registration object and a mode string as parameters. +// The Registration object contains information about the service to be registered, +// such as the namespace and service description. +// The mode string specifies the mode in which the service should be registered. +// It returns an error if the service is already registered in the specified namespace. +func (reg Registry) RegisterService(r Registration, mode string) error { + // Can't directly modify structs within a map, so we need to copy the namespace + copyNamespace := reg[r.Namespace] + copyNamespace.Mode = mode + if copyNamespace.Services == nil { + copyNamespace.Services = make([]Service, 0) } + found := slices.ContainsFunc(reg[r.Namespace].Services, func(s Service) bool { + return s.ServiceDesc.ServiceName == r.ServiceDesc.ServiceName + }) - if RegisteredServices[r.Namespace][r.ServiceDesc.ServiceName].RegisterFunc != nil { + if found { return fmt.Errorf("service already registered namespace:%s service:%s", r.Namespace, r.ServiceDesc.ServiceName) } slog.Info("registered service", slog.String("namespace", r.Namespace), slog.String("service", r.ServiceDesc.ServiceName)) - RegisteredServices[r.Namespace][r.ServiceDesc.ServiceName] = Service{ + copyNamespace.Services = append(copyNamespace.Services, Service{ Registration: r, - } + }) + + reg[r.Namespace] = copyNamespace return nil } + +// Shutdown stops all the services in the service registry. +// It iterates over each namespace and service in the registry, +// checks if the service has a Close method and if it has been started, +// and then calls the Close method to stop the service. +func (reg Registry) Shutdown() { + for name, ns := range reg { + for _, svc := range ns.Services { + if svc.Close != nil && svc.Started { + slog.Info("stopping service", slog.String("namespace", name), slog.String("service", svc.ServiceDesc.ServiceName)) + svc.Close() + } + } + } +} + +// GetNamespace returns the namespace with the given name from the service registry. +func (reg Registry) GetNamespace(namespace string) (Namespace, error) { + ns, ok := reg[namespace] + if !ok { + return Namespace{}, fmt.Errorf("namespace not found: %s", namespace) + } + return ns, nil +}