Skip to content

Commit

Permalink
Merge pull request #38 from SUSE/authenticate_request
Browse files Browse the repository at this point in the history
Add support for telemetry client (re-)authenticate request
  • Loading branch information
rtamalin authored Jul 24, 2024
2 parents be124bb + d98b46c commit 7d6373a
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 37 deletions.
12 changes: 10 additions & 2 deletions cmd/clientds/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,19 @@ func main() {
}
fmt.Printf("Config: %+v\n", cfg)

// 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")
lm.SetLevel("DEBUG")
slog.Debug("Debug mode enabled")
}
if err := lm.ConfigAndSetup(&cfg.Logging); err != nil {

if err := lm.Setup(); err != nil {
panic(err)
}

Expand Down
70 changes: 45 additions & 25 deletions cmd/generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,28 @@ import (

// options is a struct of the options
type options struct {
config string
dryrun bool
nobundles bool
noreports bool
nosubmit bool
tags types.Tags
telemetry types.TelemetryType
jsonFiles []string
debug bool
}

func (o options) String() string {
return fmt.Sprintf("config=%v, dryrun=%v, tags=%v, telemetry=%v, jsonFiles=%v, debug=%v", o.config, o.dryrun, o.tags, o.telemetry, o.jsonFiles, o.debug)
config string
dryrun bool
noregister bool
authenticate bool
nobundles bool
noreports bool
nosubmit bool
tags types.Tags
telemetry types.TelemetryType
jsonFiles []string
debug bool
}

var opts options

func main() {
fmt.Printf("Generator: %s\n", opts)

if err := logging.SetupBasicLogging(opts.debug); err != nil {
panic(err)
}

slog.Debug("Generator", slog.Any("options", opts))

cfg, err := config.NewConfig(opts.config)
if err != nil {
slog.Error(
Expand All @@ -47,13 +45,20 @@ func main() {
)
panic(err)
}
fmt.Printf("Config: %+v\n", cfg)

// 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")
lm.SetLevel("DEBUG")
slog.Debug("Debug mode enabled")
}
if err := lm.ConfigAndSetup(&cfg.Logging); err != nil {

if err := lm.Setup(); err != nil {
panic(err)
}

Expand All @@ -67,13 +72,26 @@ func main() {
panic(err)
}

err = tc.Register()
if err != nil {
slog.Error(
"Failed to register TelemetryClient",
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)
}
}

for _, jsonFile := range opts.jsonFiles {
Expand Down Expand Up @@ -137,6 +155,8 @@ 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.BoolVar(&opts.noreports, "noreports", false, "Do not create Telemetry reports")
flag.BoolVar(&opts.nobundles, "nobundles", false, "Do not create Telemetry bundles")
flag.BoolVar(&opts.nosubmit, "nosubmit", false, "Do not submit any Telemetry reports")
Expand Down
131 changes: 124 additions & 7 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,25 +125,32 @@ func (tc *TelemetryClient) InstIdPath() string {
return INSTANCEID_PATH
}

func (tc *TelemetryClient) getInstanceId() (instId []byte, err error) {
func (tc *TelemetryClient) getInstanceId() (instId types.ClientInstanceId, err error) {
instIdPath := tc.InstIdPath()

err = ensureInstanceIdExists(instIdPath)
if err != nil {
return
}

instId, err = os.ReadFile(instIdPath)
data, err := os.ReadFile(instIdPath)
if err != nil {
slog.Error(
"failed to read instId file",
slog.String("instIdPath", instIdPath),
slog.String("path", instIdPath),
slog.String("err", err.Error()),
)
} else {
slog.Info("successfully read instId file", slog.String("instId", string(instId)))
return
}

instId = types.ClientInstanceId((data))

slog.Debug(
"successfully read instId file",
slog.String("path", string(instIdPath)),
slog.String("instId", string(instId)),
)

return
}

Expand Down Expand Up @@ -182,7 +189,7 @@ func (tc *TelemetryClient) loadTelemetryAuth() (err error) {
return
}

if tc.auth.ClientId == 0 {
if tc.auth.ClientId <= 0 {
err = fmt.Errorf("invalid client id")
slog.Error(
"invalid auth",
Expand Down Expand Up @@ -248,6 +255,7 @@ func (tc *TelemetryClient) submitReport(report *telemetrylib.TelemetryReport) (e

req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", "Bearer "+tc.auth.Token.String())
req.Header.Add("X-Telemetry-Client-Id", fmt.Sprintf("%d", tc.auth.ClientId))

httpClient := http.DefaultClient
resp, err := httpClient.Do(req)
Expand Down Expand Up @@ -283,6 +291,115 @@ func (tc *TelemetryClient) submitReport(report *telemetrylib.TelemetryReport) (e
return
}

// Authenticate is responsible for (re)authenticating an already registered
// client with the server to ensure that it's auth token is up to date.
func (tc *TelemetryClient) Authenticate() (err error) {
if err = tc.loadTelemetryAuth(); err != nil {
return fmt.Errorf(
"telemetry client (re-)authentication requires an existing "+
"client registration: %s",
err.Error(),
)
}

// get the instanceId, failing if it can't be retrieved
instId, err := tc.getInstanceId()
if err != nil {
return
}

// assemble the authentication request
caReq := restapi.ClientAuthenticationRequest{
ClientId: tc.auth.ClientId,
InstIdHash: *instId.Hash("default"),
}

reqBodyJSON, err := json.Marshal(&caReq)
if err != nil {
slog.Error(
"failed to JSON marshal caReq",
slog.String("err", err.Error()),
)
return
}

reqUrl := tc.cfg.TelemetryBaseURL + "/authenticate"
reqBuf := bytes.NewBuffer(reqBodyJSON)
req, err := http.NewRequest("POST", reqUrl, reqBuf)
if err != nil {
slog.Error(
"failed to create new HTTP request for client authentication",
slog.String("err", err.Error()),
)
return
}

req.Header.Add("Content-Type", "application/json")

httpClient := http.DefaultClient
resp, err := httpClient.Do(req)
if err != nil {
slog.Error(
"failed to HTTP POST client authentication request",
slog.String("err", err.Error()),
)
return
}
defer resp.Body.Close()

respBody, err := io.ReadAll(resp.Body)
if err != nil {
slog.Error(
"failed to read client authentication response body",
slog.String("err", err.Error()),
)
return
}

if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("client authentication failed: %s", string(respBody))
return
}

var caResp restapi.ClientAuthenticationResponse
err = json.Unmarshal(respBody, &caResp)
if err != nil {
slog.Error(
"failed to JSON unmarshal client authentication response body content",
slog.String("err", err.Error()),
)
return
}

tc.auth.ClientId = caResp.ClientId
tc.auth.Token = types.TelemetryAuthToken(caResp.AuthToken)
tc.auth.IssueDate, err = types.TimeStampFromString(caResp.IssueDate)
if err != nil {
slog.Error(
"failed to parse issueDate as a timestamp",
slog.String("issueDate", caResp.IssueDate),
slog.String("err", err.Error()),
)
return
}

err = tc.saveTelemetryAuth()
if err != nil {
slog.Error(
"failed to save TelemetryAuth",
slog.String("err", err.Error()),
)
return
}

slog.Info(
"successfully authenticated",
slog.Int64("clientId", tc.auth.ClientId),
)

return
}

func (tc *TelemetryClient) Register() (err error) {
// get the saved TelemetryAuth, returning success if found
err = tc.loadTelemetryAuth()
Expand All @@ -299,7 +416,7 @@ func (tc *TelemetryClient) Register() (err error) {

// register the system as a client
var crReq restapi.ClientRegistrationRequest
crReq.ClientInstanceId = string(instId)
crReq.ClientInstanceId = instId
reqBodyJSON, err := json.Marshal(&crReq)
if err != nil {
slog.Error(
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.Info("Contents", slog.String("contents", string(contents)))
slog.Debug("Contents", 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)
Expand Down
2 changes: 1 addition & 1 deletion pkg/lib/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (p *TelemetryProcessorImpl) DeleteReport(reportRow *TelemetryReportRow) (er
var _ TelemetryProcessor = (*TelemetryProcessorImpl)(nil)

func NewTelemetryProcessor(cfg *config.DBConfig) (TelemetryProcessor, error) {
slog.Info("NewTelemetryProcessor", slog.Any("cfg", cfg))
slog.Debug("NewTelemetryProcessor", slog.Any("cfg", cfg))
p := TelemetryProcessorImpl{cfg: cfg}

err := p.setup(cfg)
Expand Down
18 changes: 17 additions & 1 deletion pkg/restapi/restapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

// ClientRegistrationRequest is the request payload body POST'd to the server
type ClientRegistrationRequest struct {
ClientInstanceId string `json:"clientInstanceId"`
ClientInstanceId types.ClientInstanceId `json:"clientInstanceId"`
}

func (c *ClientRegistrationRequest) String() string {
Expand All @@ -36,6 +36,22 @@ func (c *ClientRegistrationResponse) String() string {
return string(bytes)
}

// Client Authenticate handling via /temelemtry/authenticate
type ClientAuthenticationRequest struct {
ClientId int64 `json:"clientId"`
InstIdHash types.ClientInstanceIdHash `json:"instIdHash"`
}

func (c *ClientAuthenticationRequest) String() string {
bytes, _ := json.Marshal(c)

return string(bytes)
}

// for now the /authenticate response is the same as the /register
// response
type ClientAuthenticationResponse = ClientRegistrationResponse

//
// Client Telemetry Report via /telemetry/report POST
//
Expand Down
Loading

0 comments on commit 7d6373a

Please sign in to comment.