diff --git a/cmd/authenticator/go.mod b/cmd/authenticator/go.mod new file mode 100644 index 0000000..ab35f5d --- /dev/null +++ b/cmd/authenticator/go.mod @@ -0,0 +1,15 @@ +module github.com/SUSE/telemetry/cmd/authenticator + +go 1.21 + +replace github.com/SUSE/telemetry => ../../ + +require github.com/SUSE/telemetry v0.0.0-00010101000000-000000000000 + +require ( + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/xyproto/randomstring v1.0.5 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/cmd/authenticator/go.sum b/cmd/authenticator/go.sum new file mode 100644 index 0000000..93900fd --- /dev/null +++ b/cmd/authenticator/go.sum @@ -0,0 +1,18 @@ +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/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +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/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/authenticator/main.go b/cmd/authenticator/main.go new file mode 100644 index 0000000..f79265a --- /dev/null +++ b/cmd/authenticator/main.go @@ -0,0 +1,124 @@ +package main + +import ( + "flag" + "fmt" + "log/slog" + + "github.com/SUSE/telemetry/pkg/client" + "github.com/SUSE/telemetry/pkg/config" + "github.com/SUSE/telemetry/pkg/logging" +) + +// options is a struct of the options +type options struct { + config string + dryrun bool + noregister bool + authenticate bool + debug bool +} + +var opts options + +func main() { + if err := logging.SetupBasicLogging(opts.debug); err != nil { + panic(err) + } + + slog.Debug("Authenticator", slog.Any("options", opts)) + + cfg, err := config.NewConfig(opts.config) + if err != nil { + slog.Error( + "Failed to load config", + slog.String("config", opts.config), + slog.String("error", err.Error()), + ) + panic(err) + } + + // setup logging based upon config settings + lm := logging.NewLogManager() + if err := lm.Config(&cfg.Logging); err != nil { + panic(err) + } + + // override config log level to debug if option specified + if opts.debug { + lm.SetLevel("DEBUG") + slog.Debug("Debug mode enabled") + } + + if err := lm.Setup(); err != nil { + panic(err) + } + + tc, err := client.NewTelemetryClient(cfg) + if err != nil { + slog.Error( + "Failed to instantiate TelemetryClient", + slog.String("config", opts.config), + slog.String("error", err.Error()), + ) + panic(err) + } + + if !opts.noregister { + err = tc.Register() + if err != nil { + slog.Error( + "Failed to register TelemetryClient", + slog.String("error", err.Error()), + ) + panic(err) + } + } + + if opts.authenticate { + err = tc.Authenticate() + if err != nil { + slog.Error( + "Failed to (re)uthenticate TelemetryClient", + slog.String("error", err.Error()), + ) + panic(err) + } + } + + issuer, err := tc.AuthIssuer() + if err != nil { + slog.Error( + "AuthIssuer() failed", + slog.String("err", err.Error()), + ) + } + + expiration, err := tc.AuthExpiration() + if err != nil { + slog.Error( + "AuthExpiration() failed", + slog.String("err", err.Error()), + ) + } + + fmt.Printf( + "Current Auth Token:\n %-[1]*[2]s %[3]s\n %-[1]*[4]s %[5]s\n %-[1]*[6]s %[7]s\n", + 19, + "Issuer:", + issuer, + "Expiration (UTC):", + expiration.UTC().Format("2006-01-02T15:04:05.000000"), + "Expiration (local):", + expiration.Format("2006-01-02T15:04:05.000000Z07:00"), + ) +} + +func init() { + flag.StringVar(&opts.config, "config", client.CONFIG_PATH, "Path to config file to read") + flag.BoolVar(&opts.debug, "debug", false, "Whether to enable debug level logging.") + flag.BoolVar(&opts.dryrun, "dryrun", false, "Process provided JSON files but do add them to the telemetry staging area.") + flag.BoolVar(&opts.noregister, "noregister", false, "Whether to skip registering the telemetry client if it is needed.") + flag.BoolVar(&opts.authenticate, "authenticate", false, "Whether to (re)authenticate the telemetry client.") + flag.Parse() +} diff --git a/cmd/clientds/go.mod b/cmd/clientds/go.mod index 1949c24..ba0e794 100644 --- a/cmd/clientds/go.mod +++ b/cmd/clientds/go.mod @@ -7,6 +7,7 @@ replace github.com/SUSE/telemetry => ../../../telemetry require github.com/SUSE/telemetry v0.0.0-00010101000000-000000000000 require ( + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/xyproto/randomstring v1.0.5 // indirect diff --git a/cmd/clientds/go.sum b/cmd/clientds/go.sum index 34efe85..93900fd 100644 --- a/cmd/clientds/go.sum +++ b/cmd/clientds/go.sum @@ -1,5 +1,7 @@ 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/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= diff --git a/cmd/clientds/main.go b/cmd/clientds/main.go index 1fe1329..4d06ee8 100644 --- a/cmd/clientds/main.go +++ b/cmd/clientds/main.go @@ -19,14 +19,13 @@ type options struct { debug bool } -func (o options) String() string { - return fmt.Sprintf("config=%v, items=%v, bundles=%v, reports=%v, debug=%v", o.config, o.items, o.bundles, o.reports, o.debug) -} - var opts options func main() { - fmt.Printf("clientds: %s\n", opts) + slog.Debug( + "clientds", + slog.Any("options", opts), + ) if err := logging.SetupBasicLogging(opts.debug); err != nil { panic(err) @@ -41,7 +40,6 @@ func main() { ) panic(err) } - fmt.Printf("Config: %+v\n", cfg) // setup logging based upon config settings lm := logging.NewLogManager() @@ -71,6 +69,9 @@ func main() { processor := tc.Processor() + // this will be toggled to true if items, bundles or reports were found + foundEntries := false + if opts.items { itemRows, err := processor.GetItemRows() if err != nil { @@ -87,6 +88,8 @@ func main() { for i, dataItemRow := range itemRows { fmt.Printf("Data Item[%d]: %q\n", i, dataItemRow.ItemId) } + + foundEntries = true } } @@ -106,6 +109,8 @@ func main() { for i, bundleRow := range bundleRows { fmt.Printf("Bundle[%d]: %q\n", i, bundleRow.BundleId) } + + foundEntries = true } } @@ -125,8 +130,14 @@ func main() { for i, reportRow := range reportRows { fmt.Printf("Reports[%d]: %q\n", i, reportRow.ReportId) } + + foundEntries = true } } + + if !foundEntries { + fmt.Println("No items, bundles or reports found in client datastore") + } } func init() { diff --git a/cmd/generator/go.mod b/cmd/generator/go.mod index 753de6b..4aa5f18 100644 --- a/cmd/generator/go.mod +++ b/cmd/generator/go.mod @@ -7,6 +7,7 @@ replace github.com/SUSE/telemetry => ../../ require github.com/SUSE/telemetry v0.0.0-00010101000000-000000000000 require ( + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/xyproto/randomstring v1.0.5 // indirect diff --git a/cmd/generator/go.sum b/cmd/generator/go.sum index 34efe85..93900fd 100644 --- a/cmd/generator/go.sum +++ b/cmd/generator/go.sum @@ -1,5 +1,7 @@ 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/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= diff --git a/cmd/generator/main.go b/cmd/generator/main.go index 624b7d4..a00b0ca 100644 --- a/cmd/generator/main.go +++ b/cmd/generator/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log/slog" "os" + "path/filepath" "github.com/SUSE/telemetry/pkg/client" "github.com/SUSE/telemetry/pkg/config" @@ -114,6 +115,13 @@ func main() { ) panic(err) } + + fmt.Printf( + "Added telemetry data from %q as type %q with tags %s to local datastore\n", + filepath.Base(jsonFile), + opts.telemetry, + opts.tags, + ) } // create one or more bundles from available data items @@ -125,6 +133,8 @@ func main() { ) panic(err) } + + fmt.Println("Created telemetry bundles from pending telemetry data items") } // create one or more reports from available bundles @@ -136,6 +146,8 @@ func main() { ) panic(err) } + + fmt.Println("Created telemetry reports from pending telemetry bundles") } // create one or more reports from available bundles and then @@ -148,6 +160,8 @@ func main() { ) panic(err) } + + fmt.Println("Submitted pending telemetry reports") } } diff --git a/go.mod b/go.mod index 381caaa..b6a3540 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 replace github.com/SUSE/telemetry => ../telemetry require ( + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/mattn/go-sqlite3 v1.14.22 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index 34efe85..93900fd 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ 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/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= diff --git a/pkg/client/client.go b/pkg/client/client.go index c99cc18..bafd7f3 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -11,11 +11,13 @@ import ( "log/slog" "net/http" "os" + "time" "github.com/SUSE/telemetry/pkg/config" telemetrylib "github.com/SUSE/telemetry/pkg/lib" "github.com/SUSE/telemetry/pkg/restapi" "github.com/SUSE/telemetry/pkg/types" + "github.com/golang-jwt/jwt/v5" ) const ( @@ -33,9 +35,10 @@ type TelemetryAuth struct { } type TelemetryClient struct { - cfg *config.Config - auth TelemetryAuth - processor telemetrylib.TelemetryProcessor + cfg *config.Config + auth TelemetryAuth + authLoaded bool + processor telemetrylib.TelemetryProcessor } func NewTelemetryClient(cfg *config.Config) (tc *TelemetryClient, err error) { @@ -70,7 +73,7 @@ func checkFileReadAccessible(filePath string) bool { func ensureInstanceIdExists(instIdPath string) error { - slog.Info("ensuring existence of instIdPath", slog.String("instIdPath", instIdPath)) + slog.Debug("ensuring existence of instIdPath", slog.String("instIdPath", instIdPath)) _, err := os.Stat(instIdPath) if !os.IsNotExist(err) { return nil @@ -94,6 +97,71 @@ func ensureInstanceIdExists(instIdPath string) error { return nil } +func (tc *TelemetryClient) authParsedToken() (token *jwt.Token, err error) { + if !tc.authLoaded { + if err = tc.loadTelemetryAuth(); err != nil { + slog.Error( + "Failed to load authToken", + slog.String("error", err.Error()), + ) + return + } + } + + // only the server can validate the signing key, so parse unverified + token, _, err = jwt.NewParser().ParseUnverified( + string(tc.auth.Token), jwt.MapClaims{}, + ) + + if err != nil { + slog.Error( + "Failed to parse JWT", + slog.String("error", err.Error()), + ) + return + } + + return +} + +func (tc *TelemetryClient) AuthIssuer() (issuer string, err error) { + token, err := tc.authParsedToken() + if err != nil { + return + } + + issuer, err = token.Claims.GetIssuer() + if err != nil { + slog.Error( + "Filed to retrieve issuer from token claims", + slog.String("err", err.Error()), + ) + return + } + + return +} + +func (tc *TelemetryClient) AuthExpiration() (expiration time.Time, err error) { + token, err := tc.authParsedToken() + if err != nil { + return + } + + expTime, err := token.Claims.GetExpirationTime() + if err != nil { + slog.Error( + "Filed to retrieve expiration date from token claims", + slog.String("err", err.Error()), + ) + return + } + + expiration = expTime.Time + + return +} + func (tc *TelemetryClient) AuthAccessible() bool { return checkFileReadAccessible(tc.AuthPath()) } @@ -157,7 +225,7 @@ func (tc *TelemetryClient) getInstanceId() (instId types.ClientInstanceId, err e func (tc *TelemetryClient) loadTelemetryAuth() (err error) { authPath := tc.AuthPath() - slog.Info("Checking auth file existence", slog.String("authPath", authPath)) + slog.Debug("Checking auth file existence", slog.String("authPath", authPath)) _, err = os.Stat(authPath) if os.IsNotExist(err) { slog.Warn( @@ -211,6 +279,8 @@ func (tc *TelemetryClient) loadTelemetryAuth() (err error) { return } + tc.authLoaded = true + return } @@ -283,7 +353,7 @@ func (tc *TelemetryClient) submitReport(report *telemetrylib.TelemetryReport) (e return } - slog.Info( + slog.Debug( "successfully submitted report", slog.String("report", report.Header.ReportId), slog.String("processing", trResp.ProcessingInfo()), @@ -392,7 +462,7 @@ func (tc *TelemetryClient) Authenticate() (err error) { return } - slog.Info( + slog.Debug( "successfully authenticated", slog.Int64("clientId", tc.auth.ClientId), ) @@ -404,7 +474,7 @@ func (tc *TelemetryClient) Register() (err error) { // get the saved TelemetryAuth, returning success if found err = tc.loadTelemetryAuth() if err == nil { - slog.Info("telemetry auth found, client already registered, skipping", slog.Int64("clientId", tc.auth.ClientId)) + slog.Debug("telemetry auth found, client already registered, skipping", slog.Int64("clientId", tc.auth.ClientId)) return } @@ -487,6 +557,8 @@ func (tc *TelemetryClient) Register() (err error) { return } + tc.authLoaded = true + err = tc.saveTelemetryAuth() if err != nil { slog.Error( @@ -496,7 +568,7 @@ func (tc *TelemetryClient) Register() (err error) { return } - slog.Info( + slog.Debug( "successfully registered as client", slog.Int64("clientId", tc.auth.ClientId), ) @@ -513,7 +585,7 @@ func (tc *TelemetryClient) Generate(telemetry types.TelemetryType, content []byt } // Add telemetry data item to DataItem data store - slog.Info( + slog.Debug( "Generated Telemetry", slog.String("name", telemetry.String()), slog.String("tags", tags.String()), @@ -525,7 +597,7 @@ func (tc *TelemetryClient) Generate(telemetry types.TelemetryType, content []byt func (tc *TelemetryClient) CreateBundles(tags types.Tags) error { // Bundle existing telemetry data items found in DataItem data store into one or more bundles in the Bundle data store - slog.Info("Bundle", slog.String("Tags", tags.String())) + slog.Debug("Bundle", slog.String("Tags", tags.String())) tc.processor.GenerateBundle(tc.auth.ClientId, tc.cfg.CustomerID, tags) return nil @@ -533,7 +605,7 @@ func (tc *TelemetryClient) CreateBundles(tags types.Tags) error { func (tc *TelemetryClient) CreateReports(tags types.Tags) (err error) { // Generate reports from available bundles - slog.Info("CreateReports", slog.String("Tags", tags.String())) + slog.Debug("CreateReports", slog.String("Tags", tags.String())) tc.processor.GenerateReport(tc.auth.ClientId, tags) return diff --git a/pkg/config/config.go b/pkg/config/config.go index 0071d3b..c1a74de 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -93,7 +93,7 @@ func NewConfig(cfgFile string) (*Config, error) { return cfg, fmt.Errorf("failed to read contents of config file '%s': %s", cfgFile, err) } - slog.Debug("Contents", slog.String("contents", string(contents))) + slog.Debug("Config", slog.String("contents", string(contents))) err = yaml.Unmarshal(contents, &cfg) if err != nil { return cfg, fmt.Errorf("failed to parse contents of config file '%s': %s", cfgFile, err) diff --git a/pkg/logging/logging.go b/pkg/logging/logging.go index 6efa7fa..3b50d22 100644 --- a/pkg/logging/logging.go +++ b/pkg/logging/logging.go @@ -354,7 +354,7 @@ func (lm *LogManager) Setup() (err error) { // set our handler as the default slog handler slog.SetDefault(lm.logger) - slog.Info( + slog.Debug( "Logging initialised", slog.Any("level", lm.logLevel), slog.String("dest", lm.logPath),