From 85f7cc1b8de87ea15d01f9b087e129052dafaa25 Mon Sep 17 00:00:00 2001 From: jacov Date: Tue, 3 Mar 2026 10:09:46 +0200 Subject: [PATCH 1/4] Auth licensing License client v0.0.4 Dev vs Prod modes Update build script Documentation update NEXUS-5339 --- .gitignore | 33 ++++++++++ README.md | 131 ++++++++++++++++++++++++++++++++++++++- auth/http.go | 29 ++++++--- auth/license_dev.go | 16 +++++ auth/license_prod.go | 38 ++++++++++++ auth/license_required.go | 14 +++++ build_dev.go | 6 ++ build_prod.go | 19 ++++++ build_required.go | 9 +++ go.mod | 10 +-- go.sum | 16 +++-- imqs-build.rb | 4 +- imqsauth.go | 5 ++ testconf/imqsauth.json | 11 +++- 14 files changed, 317 insertions(+), 24 deletions(-) create mode 100644 auth/license_dev.go create mode 100644 auth/license_prod.go create mode 100644 auth/license_required.go create mode 100644 build_dev.go create mode 100644 build_prod.go create mode 100644 build_required.go diff --git a/.gitignore b/.gitignore index fa1080c2..e7eebac7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,36 @@ __debug_bin /imqsauth.exe /log/ /imqsauth.log +/licenses_client/ +/client.key +/client.pub +/tools/listanddiff/go_build_listanddiff_go.exe +/id_rsa +/key.bin +/tools/listanddiff/listanddiff.exe +/tools/listanddiff/output.csv +/server.bin +/server.key +/testconf/tempconf_live.json +/testconf/tempconf_live_imqs-entra.json +/testconf/tempconf_local.json +/server.pub +/frontend/node_modules/ +/frontend/dist/ +/licenses_client/ +/t1/ +/t2/ +/t3/ +/keys/ +/server.bin +/key.bin +/tools/listanddiff/go_build_listanddiff_go.exe +/id_rsa +/client.key +/client.pub +/test_ldap/imqsauth.exe +/imqsauth.exe.garble +/tools/listanddiff/listanddiff.exe +/httpfront/static/assets/ +/tools/listanddiff/output.csv +/test_ldap/log/imqsauth.log diff --git a/README.md b/README.md index 591b8039..1904ed47 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,134 @@ More information can be found in Confluence: [Imqs Auth](https://imqssoftware.at ## Building -To build imqsauth: +### Quick build - go build imqsauth.go +A build tag must **always** be specified — omitting it is a compile error: + + go build -tags prod . # production (requires server.bin and key.bin) + go build -tags dev . # development (no license required, no key files needed) + +### Dev build (no license required) + +For local development, build with the `dev` tag. This skips the `//go:embed` +key file requirement entirely and disables all license checks at runtime — +no `server.bin`, `key.bin`, or `licenses_client` folder needed: + + go build -tags dev -o imqsauth.exe . + +A warning is printed on startup to make it clear the service is running in +dev mode: + + DEV MODE: license checks are disabled (built with -tags dev or no key files embedded) + +**Do not use a dev build in production.** + +### License Management Considerations + +The build requires two binary files to be present in the root of the repository +before `go build` is run, because they are compiled directly into the binary via +`//go:embed`: + +| File | Purpose | +|---|---| +| `server.bin` | XOR-masked server public key used by the LicenseClient library | +| `key.bin` | XOR mask used at runtime to recover the real public key from `server.bin` | + +Both files are excluded from source control (`.gitignore`). See +[Generating the embedded key files](#generating-the-embedded-key-files) below. + +### Build pipeline + +We use an older version of Jenkins at the moment, which supports specifying +a secrets _file_. + +- Upload both `server.bin` and `key.bin` to Jenkins as **Secret file** credentials + (**not** Secret text — Secret text stores the file *contents* as a string, so + the bound variable will contain binary data instead of a file path, causing + `copy` to fail with *"The system cannot find the file specified"*): + - Go to **Jenkins → Manage Jenkins → Credentials → (store) → Add Credentials** + - Set **Kind** = `Secret file`, upload the file, set the ID: + - _LICENSE_CLIENT_KEY_BIN_ — upload `key.bin` + - _LICENSE_CLIENT_SERVER_BIN_ — upload `server.bin` +- Add them to the build pipeline as secret files, and assign them to named + environment variables: + - _LICENSE_CLIENT_KEY_BIN_ → `KEY_BIN` + - _LICENSE_CLIENT_SERVER_BIN_ → `SERVER_BIN` +- Copy the files into the correct location before building using **Windows Batch** + commands. Jenkins injects the secret file path using forward slashes, which the + Windows `copy` command does not handle correctly as a source path — convert them + to backslashes first: + ```bat + set KEY_BIN_WIN=%KEY_BIN:/=\% + set SERVER_BIN_WIN=%SERVER_BIN:/=\% + copy "%KEY_BIN_WIN%" "%WORKSPACE%\imqsauth\key.bin" + copy "%SERVER_BIN_WIN%" "%WORKSPACE%\imqsauth\server.bin" + cd %WORKSPACE%\imqsauth && go build -tags prod -o imqsauth.exe . + ``` + > **Note:** if `imqsauth` is checked out as a submodule inside a larger + > workspace (e.g. `C:\Jenkins\workspace\Build-RC\imqsauth\`), the destination + > path should include the submodule folder as shown above. If this repository + > *is* the workspace root, use `%WORKSPACE%\key.bin` / `%WORKSPACE%\server.bin` + > directly. Either way the files must end up alongside `imqsauth.go`, because + > the `go:embed` directives resolve paths relative to the `.go` source file. +- Clean up afterwards by deleting the files from the workspace: + ```bat + del "%WORKSPACE%\imqsauth\key.bin" + del "%WORKSPACE%\imqsauth\server.bin" + ``` + + +### Go module dependencies + +All Go dependencies are declared in `go.mod` and fetched automatically by the +Go toolchain. The key external library for license management is: + +- `github.com/IMQS/licenseserver v0.0.4` — provides the `LicenseClient` used + to validate the `enterprise` license at runtime. Consumed via two sub-packages: + - `github.com/IMQS/licenseserver/client` — `LicenseClient` struct + - `github.com/IMQS/licenseserver/lib` — `UnmaskPublicKey()` and shared logger + +Run `go mod download` to fetch all dependencies before building offline. + +### Generating the embedded key files + +`server.bin` and `key.bin` are produced from the plaintext public key file +`server.pub` using `xortool`. The source for `xortool` lives in the +`github.com/IMQS/licenseserver` repository. Once you have `xortool.go`: + + go build xortool.go + xortool ./server.pub + +This writes `server.bin` (the XOR-encoded key) and `key.bin` (the XOR mask) +into the current directory. After that, `go build -tags prod .` will succeed. + +### Building the license tooling (`build.bat`) + +`build.bat` automates building the supporting license tools. It requires source +files from the `licenseserver` repository (`xortool.go`, `licenseclient.go`, +`licensetool.go`). + + rem Build and run xortool to produce server.bin and key.bin from server.pub: + go build xortool.go + xortool ./server.pub + + rem Build the license client helper: + go build licenseclient.go + +> **Note:** `build.bat` does **not** build `imqsauth.exe` itself. Run +> `go build -tags prod .` separately after the key files have been generated. + +_Garble_ + +We CAN support `garble` (a Go code obfuscator), but there are +problems with anti-virus scanning, especially in production environments and is +currently not used. + + rem Install garble (code obfuscation tool): + go install mvdan.cc/garble@v0.15.0 + + rem Build the license tool with literal obfuscation: + garble --literals build licensetool.go ## Testing @@ -25,7 +150,7 @@ in it's own CI job. The following block demonstrates running all of the tests: go test github.com/IMQS/imqsauth/auth - go build imqsauth.go + go build -tags dev . gem install rest-client ruby resttest.rb diff --git a/auth/http.go b/auth/http.go index 1bc9837d..0ce4657e 100644 --- a/auth/http.go +++ b/auth/http.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "golang.org/x/exp/slices" "io/ioutil" "net/http" "net/url" @@ -14,6 +13,8 @@ import ( "sync" "time" + "golang.org/x/exp/slices" + "github.com/IMQS/authaus" "github.com/IMQS/imqsauth/utils" "github.com/IMQS/serviceauth" @@ -54,6 +55,7 @@ const ( handlerFlagNeedAdminRights = 1 << iota // Request won't even reach your handler unless the user is an admin handlerFlagNeedToken // Populate the httpRequest object with 'token' and 'permList' handlerFlagNeedInterService // Request requires interservice authorization + handlerFlagNoLicense // Request does not require the service to be licensed, so we can still gather metrics/status ) type httpRequest struct { @@ -157,6 +159,16 @@ func (x *ImqsCentral) makeHandler(method HttpMethod, actual func(*ImqsCentral, h needAdmin := 0 != (flags & handlerFlagNeedAdminRights) needToken := 0 != (flags & handlerFlagNeedToken) needInterService := 0 != (flags & handlerFlagNeedInterService) + needLicense := !(0 != (flags & handlerFlagNoLicense)) + + if needLicense { + isValid := isLicensed() + if !isValid { + authaus.HttpSendTxt(w, http.StatusPaymentRequired, "This service is not licensed to run. Please contact your vendor.") + return + } + } + if !needAdmin && !needToken && !needInterService { actual(x, w, httpReq) return @@ -211,14 +223,17 @@ func (x *ImqsCentral) makeHandler(method HttpMethod, actual func(*ImqsCentral, h } func (x *ImqsCentral) RunHttp() error { + if err := x.initLicenseClient(); err != nil { + return err + } // The built-in go ServeMux does not support differentiating based on HTTP verb, so we have to make // the request path unique for each verb. I think this is OK as far as API design is concerned - at least in this domain. smux := http.NewServeMux() - smux.HandleFunc("/hello", x.makeHandler(HttpMethodGet, httpHandlerHello, 0)) - smux.HandleFunc("/ping", x.makeHandler(HttpMethodGet, httpHandlerPing, 0)) - smux.HandleFunc("/hostname", x.makeHandler(HttpMethodGet, httpHanderHostname, 0)) + smux.HandleFunc("/hello", x.makeHandler(HttpMethodGet, httpHandlerHello, handlerFlagNoLicense)) + smux.HandleFunc("/ping", x.makeHandler(HttpMethodGet, httpHandlerPing, handlerFlagNoLicense)) + smux.HandleFunc("/hostname", x.makeHandler(HttpMethodGet, httpHanderHostname, handlerFlagNoLicense)) smux.HandleFunc("/login", x.makeHandler(HttpMethodPost, httpHandlerLogin, 0)) - smux.HandleFunc("/logout", x.makeHandler(HttpMethodPost, httpHandlerLogout, 0)) + smux.HandleFunc("/logout", x.makeHandler(HttpMethodPost, httpHandlerLogout, handlerFlagNoLicense)) smux.HandleFunc("/check", x.makeHandler(HttpMethodGet, httpHandlerCheck, 0)) smux.HandleFunc("/create_user", x.makeHandler(HttpMethodPut, httpHandlerCreateUser, handlerFlagNeedAdminRights)) smux.HandleFunc("/update_user", x.makeHandler(HttpMethodPost, httpHandlerUpdateUser, handlerFlagNeedAdminRights)) @@ -238,10 +253,10 @@ func (x *ImqsCentral) RunHttp() error { smux.HandleFunc("/users", x.makeHandler(HttpMethodGet, httpHandlerGetEmails, handlerFlagNeedToken)) smux.HandleFunc("/userobjects", x.makeHandler(HttpMethodGet, httpHandlerGetUsers, handlerFlagNeedAdminRights)) smux.HandleFunc("/userobject", x.makeHandler(HttpMethodGet, httpHandlerGetUser, handlerFlagNeedAdminRights)) - smux.HandleFunc("/groups", x.makeHandler(HttpMethodGet, httpHandlerGetGroups, 0)) + smux.HandleFunc("/groups", x.makeHandler(HttpMethodGet, httpHandlerGetGroups, handlerFlagNoLicense)) smux.HandleFunc("/exportgroups", x.makeHandler(HttpMethodGet, httpHandlerExportUserGroups, handlerFlagNeedAdminRights)) smux.HandleFunc("/importgroups", x.makeHandler(HttpMethodPost, httpHandlerImportUserGroups, handlerFlagNeedInterService)) - smux.HandleFunc("/hasactivedirectory", x.makeHandler(HttpMethodGet, httpHandlerHasActiveDirectory, 0)) + smux.HandleFunc("/hasactivedirectory", x.makeHandler(HttpMethodGet, httpHandlerHasActiveDirectory, handlerFlagNoLicense)) smux.HandleFunc("/groups_perm_names", x.makeHandler(HttpMethodGet, httpHandlerGetGroupsPermNames, handlerFlagNeedAdminRights)) smux.HandleFunc("/dynamic_permissions", x.makeHandler(HttpMethodGet, httpHandlerGetDynamicPermissions, 0)) smux.HandleFunc("/oauth/providers", x.makeHandler(HttpMethodGet, httpHandlerOAuthProviders, 0)) diff --git a/auth/license_dev.go b/auth/license_dev.go new file mode 100644 index 00000000..614a1e0d --- /dev/null +++ b/auth/license_dev.go @@ -0,0 +1,16 @@ +//go:build dev + +package imqsauth + +// Dev mode: license client is not compiled in. All license checks pass unconditionally. + +func SetLicenseKeys(p, m []byte) {} // no-op in dev mode + +func (x *ImqsCentral) initLicenseClient() error { + x.Central.Log.Warnf("DEV MODE: license checks are disabled (built with -tags dev)") + return nil +} + +func isLicensed() bool { + return true +} diff --git a/auth/license_prod.go b/auth/license_prod.go new file mode 100644 index 00000000..fc0db7ce --- /dev/null +++ b/auth/license_prod.go @@ -0,0 +1,38 @@ +//go:build prod + +package imqsauth + +import ( + "github.com/IMQS/licenseserver/client" + "github.com/IMQS/licenseserver/lib" +) + +var pk []byte +var mask []byte + +// SetLicenseKeys is called from package main's init() to supply the embedded +// key bytes without exposing them as fields on ImqsCentral. +func SetLicenseKeys(p, m []byte) { + pk = p + mask = m +} + +var licenseClient *client.LicenseClient + +func (x *ImqsCentral) initLicenseClient() error { + serverPub, e := lib.UnmaskPublicKey(pk, mask) + if e != nil { + return e + } + licenseClient = &client.LicenseClient{} + licenseClient.Init("./licenses_client", serverPub) + licenseClient.LicenseServerURL = "https://deploy.imqs.co.za/licenses/" + licenseClient.Logger = x.Central.Log + lib.L = x.Central.Log + licenseClient.RunClient() + return nil +} + +func isLicensed() bool { + return licenseClient.IsLicensed("enterprise") +} diff --git a/auth/license_required.go b/auth/license_required.go new file mode 100644 index 00000000..1c303950 --- /dev/null +++ b/auth/license_required.go @@ -0,0 +1,14 @@ +//go:build !dev && !prod + +package imqsauth + +// Intentional compile error: a build tag must be specified explicitly. +// Use: +// go build -tags dev . (development — no license required, no key files needed) +// go build -tags prod . (production — requires server.bin and key.bin) +var _ = ERROR_must_specify_a_build_tag__use_tags_dev_or_tags_prod + +// Stubs to suppress cascading errors — the var above is the only intended message. +func SetLicenseKeys(p, m []byte) { } +func (x *ImqsCentral) initLicenseClient() error { return nil } +func isLicensed() bool { return false } diff --git a/build_dev.go b/build_dev.go new file mode 100644 index 00000000..3e101753 --- /dev/null +++ b/build_dev.go @@ -0,0 +1,6 @@ +//go:build dev + +package main + +// Dev mode: no embedded keys. auth.SetLicenseKeys is a no-op. +// Build with: go build -tags dev . diff --git a/build_prod.go b/build_prod.go new file mode 100644 index 00000000..2e3564e3 --- /dev/null +++ b/build_prod.go @@ -0,0 +1,19 @@ +//go:build prod + +package main + +import ( + _ "embed" + + auth "github.com/IMQS/imqsauth/auth" +) + +//go:embed server.bin +var pk []byte + +//go:embed key.bin +var mask []byte + +func init() { + auth.SetLicenseKeys(pk, mask) +} diff --git a/build_required.go b/build_required.go new file mode 100644 index 00000000..16cdbd46 --- /dev/null +++ b/build_required.go @@ -0,0 +1,9 @@ +//go:build !dev && !prod + +package main + +// Intentional compile error: a build tag must be specified explicitly. +// Use: +// go build -tags dev . (development - no license required) +// go build -tags prod . (production - requires server.bin and key.bin) +var _ = ERROR_must_specify_a_build_tag__use_tags_dev_or_tags_prod diff --git a/go.mod b/go.mod index 198b8371..83913778 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,13 @@ module github.com/IMQS/imqsauth -go 1.22.7 +go 1.24.0 require ( github.com/IMQS/authaus v1.3.9 github.com/IMQS/cli v1.2.0 github.com/IMQS/gowinsvc v1.2.0 - github.com/IMQS/log v1.4.0 + github.com/IMQS/licenseserver v0.0.4 + github.com/IMQS/log v1.5.1 github.com/IMQS/serviceauth v1.4.0 github.com/IMQS/serviceconfigsgo v1.4.0 golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c @@ -15,19 +16,20 @@ require ( require ( github.com/BurntSushi/migration v0.0.0-20140125045755-c45b897f1335 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/denisbrodbeck/machineid v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mavricknz/asn1-ber v0.0.0-20151103223136-b9df1c2f4213 // indirect github.com/mavricknz/ldap v0.0.0-20160227184754-f5a958005e43 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/stretchr/testify v1.11.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/wI2L/jsondiff v0.6.1 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.41.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d457a9da..07f1172f 100644 --- a/go.sum +++ b/go.sum @@ -6,14 +6,18 @@ github.com/IMQS/cli v1.2.0 h1:EiE9quapc4oTqsxTvePXUYItjQqHY1wvYowv9yVwdQ4= github.com/IMQS/cli v1.2.0/go.mod h1:akJbgr8eL96PMWu+emyhyHXayVXRbhT3j0vADzfQwt0= github.com/IMQS/gowinsvc v1.2.0 h1:kcz6vm2NxLYpkaDZJ893zmD/ekh/MyZnR2arTnWIXZA= github.com/IMQS/gowinsvc v1.2.0/go.mod h1:o52o7JAKlJRwJgZD4F8Ld9u835cerQk6gpINGsZHdac= -github.com/IMQS/log v1.4.0 h1:7l0zromSMkMIqNMjeiPwL2BObZkO0X0LfoKWR/CdImE= -github.com/IMQS/log v1.4.0/go.mod h1:EVm4FzOIBh22Ucdy4n01j725B85Z7We3LaRKCVozvy8= +github.com/IMQS/licenseserver v0.0.4 h1:iYCkeAb62fJVemakkM5M3Em2+idL379H8Z6NtJYb3CI= +github.com/IMQS/licenseserver v0.0.4/go.mod h1:mfwHu3bIWYsMJkO+mitT0GPKsQ+nd7m5KdrJuQ2sfTw= +github.com/IMQS/log v1.5.1 h1:MrM5Cn4zUiH/cZqOd4A64sHrF+GldjN8UXOhiRKFRMc= +github.com/IMQS/log v1.5.1/go.mod h1:EVm4FzOIBh22Ucdy4n01j725B85Z7We3LaRKCVozvy8= github.com/IMQS/serviceauth v1.4.0 h1:Vkp7UW1xpWxsLEVHrER7cEullktcfm/DuRVWoU94z0Q= github.com/IMQS/serviceauth v1.4.0/go.mod h1:/TLp5neYrw/RkwztJHCJzdrb/YaW+uM3ao+HCvsW5Ps= github.com/IMQS/serviceconfigsgo v1.4.0 h1:SDUrElceM+USiahvPjyaOHMMvzs2W9mjaKTX0F/yG2o= github.com/IMQS/serviceconfigsgo v1.4.0/go.mod h1:XKlWlm9voI4/6XeXhMDd0q5JzZT0pMxU+3JDef9y9u4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= +github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -26,8 +30,8 @@ github.com/mavricknz/ldap v0.0.0-20160227184754-f5a958005e43 h1:x4SDcUPDTMzuFEdW github.com/mavricknz/ldap v0.0.0-20160227184754-f5a958005e43/go.mod h1:z76yvVwVulPd8FyifHe8UEHeud6XXaSan0ibi2sDy6w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -44,8 +48,8 @@ golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= diff --git a/imqs-build.rb b/imqs-build.rb index c0590277..d14a6d15 100644 --- a/imqs-build.rb +++ b/imqs-build.rb @@ -20,9 +20,9 @@ def exec_or_die( cmd, current_dir = nil ) when "build" then # 'build' exists solely for CI integration. We can't use "prepare" in that case, because "../out/bin" doesn't exist on a CI build. # OK.. this is now a legacy thing, after having creating the "copy_out" phase. - exec_or_die( "go build imqsauth.go" ) + exec_or_die( "go build -o imqsauth.exe -tags prod ." ) when "prepare" then - exec_or_die( "go build imqsauth.go" ) + exec_or_die( "go build -o imqsauth.exe -tags prod ." ) when "copy_out" then FileUtils.cp( "imqsauth.exe", out_dir + '/bin/' ) when "test_unit" then diff --git a/imqsauth.go b/imqsauth.go index 03a3ea47..2ed1bcce 100644 --- a/imqsauth.go +++ b/imqsauth.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/IMQS/log" + "github.com/IMQS/authaus" "github.com/IMQS/cli" "github.com/IMQS/gowinsvc/service" @@ -17,6 +19,7 @@ import ( serviceconfig "github.com/IMQS/serviceconfigsgo" ) + func isRunningOnLinuxOutsideOfDocker() bool { return !serviceconfig.IsContainer() && runtime.GOOS != "windows" } @@ -93,6 +96,7 @@ func exec(cmd string, args []string, options cli.OptionSet) int { }() ic := &auth.ImqsCentral{} + ic.Config = &auth.Config{} configFile := options["c"] @@ -128,6 +132,7 @@ func exec(cmd string, args []string, options cli.OptionSet) int { if createCentral { var err error ic.Central, err = authaus.NewCentralFromConfig(&ic.Config.Authaus) + ic.Central.Log.Level = log.Debug if err != nil { panic(err) } diff --git a/testconf/imqsauth.json b/testconf/imqsauth.json index e2da6634..a05927d8 100644 --- a/testconf/imqsauth.json +++ b/testconf/imqsauth.json @@ -1,7 +1,8 @@ { "Authaus": { "Log": { - "Filename": "./log/imqsauth.log" + "Filename": "./log/imqsauth.log", + "DebugLevel": "debug" }, "HTTP": { "Bind": "0.0.0.0", @@ -12,7 +13,13 @@ "Host": "127.0.0.1", "Database": "unittest_auth", "User": "unittest_user", - "Password": "unittest_password" + "Password": "unittest_password", + "SSL": false, + "Port": 5432 } + }, + "Permissions": { + "disable": [], + "dynamic": [] } } From 8e77dcabaef54897231b6b1a969640d4dab1bfac Mon Sep 17 00:00:00 2001 From: jacov Date: Thu, 11 Jun 2026 22:44:28 +0200 Subject: [PATCH 2/4] GitHub secrets handling Update GitHub actions for license keys --- .github/workflows/action.yml | 4 ++++ Dockerfile | 6 ++++-- README.md | 8 +++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index f86b5ad3..8ad2d14c 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -42,6 +42,10 @@ jobs: with: username: ${{ secrets.DEPLOY_DOCKER_USERNAME }} password: ${{ secrets.DEPLOY_DOCKER_PASSWORD }} + - name: Provide licensing secrets + run: | + echo "${{ secrets.KEY_BIN }}" | base64 --decode > ./key.bin + echo "${{ secrets.SERVER_BIN }}" | base64 --decode > ./server.bin - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: build-push diff --git a/Dockerfile b/Dockerfile index a0c57324..5c38d8fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ ################################## # Builder image ################################## -FROM golang:1.22 AS builder +FROM golang:1.24 AS builder # Authorize SSH Host RUN mkdir -p /root/.ssh && \ @@ -24,7 +24,7 @@ RUN --mount=type=ssh \ # Compile COPY . /build/ -RUN go build imqsauth.go +RUN go build -o imqsauth -tags prod . ################################## # Deployed image @@ -35,6 +35,8 @@ RUN mkdir -p /etc/imqsbin RUN mkdir -p /var/log/imqs/ RUN mkdir -p /var/imqs/secrets COPY --from=builder /build/imqsauth /opt/imqsauth +#COPY --from=builder /build/key.bin /opt/key.bin +#COPY --from=builder /build/server.bin /opt/server.bin EXPOSE 80 diff --git a/README.md b/README.md index 1904ed47..8e28014d 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ before `go build` is run, because they are compiled directly into the binary via Both files are excluded from source control (`.gitignore`). See [Generating the embedded key files](#generating-the-embedded-key-files) below. -### Build pipeline +### Build pipeline (WINDOWS) We use an older version of Jenkins at the moment, which supports specifying a secrets _file_. @@ -87,6 +87,12 @@ a secrets _file_. del "%WORKSPACE%\imqsauth\server.bin" ``` +### Build pipeline (DOCKER/GitHub Actions) + +- Convert the key.bin and server.bin files to base64. +- Add KEY_BIN and SERVER_BIN as secrets to the _repository_ actions secrets and add + their base64 values as the secret values. +- See actions.yml for details on how the files are used in the build pipeline. ### Go module dependencies From 4fe076ed12a11d771c0c7a05e7e71d98624b8d3c Mon Sep 17 00:00:00 2001 From: jacov Date: Wed, 17 Jun 2026 23:27:40 +0200 Subject: [PATCH 3/4] Downgrade to go 1.22.7 Fix Docker machine id Update to licenseserver v1.0.1 to downgrade log to v1.4.0 Update README.md to list the new volume mapping requirement. --- Dockerfile | 2 -- README.md | 31 +++++++++++++++++++++++++++++++ go.mod | 10 +++++----- go.sum | 16 ++++++++-------- imqsauth.go | 10 +++------- 5 files changed, 47 insertions(+), 22 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5c38d8fb..c9144b8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,8 +35,6 @@ RUN mkdir -p /etc/imqsbin RUN mkdir -p /var/log/imqs/ RUN mkdir -p /var/imqs/secrets COPY --from=builder /build/imqsauth /opt/imqsauth -#COPY --from=builder /build/key.bin /opt/key.bin -#COPY --from=builder /build/server.bin /opt/server.bin EXPOSE 80 diff --git a/README.md b/README.md index 8e28014d..9669901f 100644 --- a/README.md +++ b/README.md @@ -195,3 +195,34 @@ The auth service is capable of detecting whether it is running inside or outside upon startup. It leverages the service discovery mechanism in the config service to transparently rewrite database connection configurations, as well as other serviceconfig utils to detect whether or not it is inside the auth service. + +### Docker volume mapping — licenseserver 1.0.1+ + +Starting from **licenseserver 1.0.1**, the `LicenseClient` stores the client keypair +(`client.key` and `client.pub`) under `/keys` inside the container. The service manages +these files itself — it will create them on first run and may regenerate them if they do +not match. Only the **folder** needs to be mapped; do not mount the individual files. + +Mount a persistent host directory to `/keys` so the keypair survives container restarts: + +**Docker run:** +```sh +docker run \ + -v /path/to/keys:/keys \ + imqs/auth:latest +``` + +**Docker Compose:** +```yaml +services: + auth: + image: imqs/auth:latest + volumes: + - /path/to/keys:/keys +``` + +> **Note:** If you are on an older version of licenseserver (pre-1.0.1) this mapping is +> not required. The change is **breaking** — without a persistent volume at `/keys` the +> service will regenerate its keypair on every restart, which will invalidate any +> previously issued tokens. + diff --git a/go.mod b/go.mod index 83913778..218e6230 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/IMQS/imqsauth -go 1.24.0 +go 1.22.7 require ( github.com/IMQS/authaus v1.3.9 github.com/IMQS/cli v1.2.0 github.com/IMQS/gowinsvc v1.2.0 - github.com/IMQS/licenseserver v0.0.4 - github.com/IMQS/log v1.5.1 + github.com/IMQS/licenseserver v1.0.1 + github.com/IMQS/log v1.4.0 github.com/IMQS/serviceauth v1.4.0 github.com/IMQS/serviceconfigsgo v1.4.0 golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c @@ -18,7 +18,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/denisbrodbeck/machineid v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/lib/pq v1.10.9 // indirect + github.com/lib/pq v1.12.3 // indirect github.com/mavricknz/asn1-ber v0.0.0-20151103223136-b9df1c2f4213 // indirect github.com/mavricknz/ldap v0.0.0-20160227184754-f5a958005e43 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -29,7 +29,7 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect github.com/wI2L/jsondiff v0.6.1 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/sys v0.41.0 // indirect + golang.org/x/sys v0.30.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 07f1172f..803b2a04 100644 --- a/go.sum +++ b/go.sum @@ -6,10 +6,10 @@ github.com/IMQS/cli v1.2.0 h1:EiE9quapc4oTqsxTvePXUYItjQqHY1wvYowv9yVwdQ4= github.com/IMQS/cli v1.2.0/go.mod h1:akJbgr8eL96PMWu+emyhyHXayVXRbhT3j0vADzfQwt0= github.com/IMQS/gowinsvc v1.2.0 h1:kcz6vm2NxLYpkaDZJ893zmD/ekh/MyZnR2arTnWIXZA= github.com/IMQS/gowinsvc v1.2.0/go.mod h1:o52o7JAKlJRwJgZD4F8Ld9u835cerQk6gpINGsZHdac= -github.com/IMQS/licenseserver v0.0.4 h1:iYCkeAb62fJVemakkM5M3Em2+idL379H8Z6NtJYb3CI= -github.com/IMQS/licenseserver v0.0.4/go.mod h1:mfwHu3bIWYsMJkO+mitT0GPKsQ+nd7m5KdrJuQ2sfTw= -github.com/IMQS/log v1.5.1 h1:MrM5Cn4zUiH/cZqOd4A64sHrF+GldjN8UXOhiRKFRMc= -github.com/IMQS/log v1.5.1/go.mod h1:EVm4FzOIBh22Ucdy4n01j725B85Z7We3LaRKCVozvy8= +github.com/IMQS/licenseserver v1.0.1 h1:A6F7ilgE1YyWusxZhIFdox7e2IL8OpjrGXYNTWuehDA= +github.com/IMQS/licenseserver v1.0.1/go.mod h1:FpScXdpGqx1PRSNOrurEi49mqjkEhoIphEDlmZNQssY= +github.com/IMQS/log v1.4.0 h1:7l0zromSMkMIqNMjeiPwL2BObZkO0X0LfoKWR/CdImE= +github.com/IMQS/log v1.4.0/go.mod h1:EVm4FzOIBh22Ucdy4n01j725B85Z7We3LaRKCVozvy8= github.com/IMQS/serviceauth v1.4.0 h1:Vkp7UW1xpWxsLEVHrER7cEullktcfm/DuRVWoU94z0Q= github.com/IMQS/serviceauth v1.4.0/go.mod h1:/TLp5neYrw/RkwztJHCJzdrb/YaW+uM3ao+HCvsW5Ps= github.com/IMQS/serviceconfigsgo v1.4.0 h1:SDUrElceM+USiahvPjyaOHMMvzs2W9mjaKTX0F/yG2o= @@ -22,8 +22,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mavricknz/asn1-ber v0.0.0-20151103223136-b9df1c2f4213 h1:3DongGRjJZvIFDq063tg76LKlGhA7O0TVqoPql0Zfbk= github.com/mavricknz/asn1-ber v0.0.0-20151103223136-b9df1c2f4213/go.mod h1:v/ZufymxjcI3pnNmQIUQQKxnHLTblrjZ4MNLs5DrZ1o= github.com/mavricknz/ldap v0.0.0-20160227184754-f5a958005e43 h1:x4SDcUPDTMzuFEdWe5lTznj1echpsd0ApTkZOdwtm7g= @@ -48,8 +48,8 @@ golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= diff --git a/imqsauth.go b/imqsauth.go index 2ed1bcce..239e6463 100644 --- a/imqsauth.go +++ b/imqsauth.go @@ -9,8 +9,6 @@ import ( "strings" "time" - "github.com/IMQS/log" - "github.com/IMQS/authaus" "github.com/IMQS/cli" "github.com/IMQS/gowinsvc/service" @@ -19,7 +17,6 @@ import ( serviceconfig "github.com/IMQS/serviceconfigsgo" ) - func isRunningOnLinuxOutsideOfDocker() bool { return !serviceconfig.IsContainer() && runtime.GOOS != "windows" } @@ -95,9 +92,9 @@ func exec(cmd string, args []string, options cli.OptionSet) int { } }() - ic := &auth.ImqsCentral{} - - ic.Config = &auth.Config{} + ic := &auth.ImqsCentral{ + Config: &auth.Config{}, + } configFile := options["c"] @@ -132,7 +129,6 @@ func exec(cmd string, args []string, options cli.OptionSet) int { if createCentral { var err error ic.Central, err = authaus.NewCentralFromConfig(&ic.Config.Authaus) - ic.Central.Log.Level = log.Debug if err != nil { panic(err) } From 42784d70dc1a3e0d72cf6379e1c0b53acfe5a9c9 Mon Sep 17 00:00:00 2001 From: jacov Date: Thu, 18 Jun 2026 23:26:41 +0200 Subject: [PATCH 4/4] Update changelog --- docs/changelog.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 3914091f..4a497525 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,17 @@ # Changelog +## Current (~v2.0.0) + +* feat!: Add licensing support to imqsauth +* feat!: CICD piplines enabled for prod builds (Jenkins / GitHub Actions) + +## 2026.1.0 (windows) + +* docs: OpenAPI spec overhaul +* fix: GitHub Actions workflow fixes with supported actions versions and LFS support +* fix: Dockerfile ssh secrets injection +* added: Add SQL query to retrieve users based on their groups + ## v1.9.0 * fix: Fix 'showidentities' command line option (NEXUS-4832)