Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework client registration and clientId management #53

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The following topics are documented:
* [Telemetry Types](telemetrytype.md)
* [Telemetry Tags](telemetrytag.md)
* [Telemetry Timestamps](telemetrytimestamp.md)
* [Telemetry Client Ids](telemetryclientid.md)
* [Telemetry Client Registration](telemetryclientregistration.md)
* [Telemetry Registration Ids](telemetryregistrationid.md)
* [Telemetry Relay](telemetryrelay.md)
* [Telemetry Client REST API](api/)
16 changes: 8 additions & 8 deletions doc/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ A telemetry client is expected to support the following workflow:

# Registration
For a telemetry client to be able to register with an upstream telemetry
server, it will need to generate a clientInstanceId value, which is used
to uniquely identify a given client with the upstream server, and should
server, it will need to generate a registration value, which is used to
uniquely identify a given client system with the upstream server, and should
store this value in a secure fashion so that it can be accessed later when
(re-)authenticating with the upstream telemetry server.

When a telemetry client registers with the upstream telemetry server,
using the [/register](requests/register.md) request, it will send a request payload
containing the clientInstanceId, and the successful response will provide
containing the registration, and the successful response will provide
a set of client credentials as follows:

| Name | Type | Description |
| ---- | ---- | ----------- |
| clientId | integer($int64) | ID used to identify the client to the server |
| authToken | string($([JWT](https://jwt.io/)) | A JSON Web Token ([JWT](https://jwt.io/)) authorization token |
| registrationId | integer($int64) | ID used to identify the client system to the server |
| authToken | string($([jwt](https://jwt.io/)) | A JSON Web Token ([JWT](https://jwt.io/)) authorization token |
| registrationDate | string($[rfc3339nano](https://pkg.go.dev/time#pkg-constants)) | The client UTC registration timestamp expressed in<br>[RFC3339nano](https://pkg.go.dev/time#pkg-constants) format |

***NOTE***: The telemetry client is responsible for storing these client
Expand All @@ -41,7 +41,7 @@ server it must prove that it has the authorization to do so. This is
achieved by supplying the appropriate request headers:

* [Authorization](headers/authorization.md)
* [X-Telemetry-Client-Id](headers/telemetry-client-id.md)
* [X-Telemetry-Registration-Id](headers/telemetry-registration-id.md)

When a telemetry client submits a telemetry report to the upstream
telemetry server, using the [/report](requests/report.md)
Expand All @@ -54,11 +54,11 @@ objects.
# (Re-)Authentication
For a telemetry client to (re-)authenticate with an upstream telemetry
server, it will need to generate a supported hash, e.g. `sha256`, of the
clientInstanceId to validate that it is in fact that client in question.
registration to validate that it is in fact that client in question.

When a telemetry client (re-)authenticates with the upstream telemetry
server, using the [/authenticate](requests/authenticate.md) request, it will
send a request payload containing it's clientId and an instIdHash,
send a request payload containing it's registratiionId and an registrationHash,
specifying the hash method and associated value, and the successful
response will provide a set of client credentials, the same as for a
[/register](requests/register.md) request, as described [above][#registration].
9 changes: 0 additions & 9 deletions doc/api/headers/telemetry-client-id.md

This file was deleted.

10 changes: 10 additions & 0 deletions doc/api/headers/telemetry-registration-id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Telemetry Registration Id Header
When a telemetry client is submitting a telemetry report using the
[/report](../requests/report.md) request it will need to provide a
`X-Telemetry-Registration-Id` header specifying the registrationId
from the client credentials obtained using the
[/register](../requests/register.md) request.

## Format of the Telemetry Registration Id Header
The `X-Telemetry-Registration-Id` header value should be the string
representation of the int64 registrationId value.
8 changes: 4 additions & 4 deletions doc/api/requests/authenticate.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ Type: **POST**

| Name | Type | Description | Example |
| ---- | ---- | ----------- | ------- |
| body | object | {<br>&nbsp;&nbsp;clientId integer($int64)<br>&nbsp;&nbsp;instIdHash {<br>&nbsp;&nbsp;&nbsp;&nbsp;method string<br>&nbsp;&nbsp;&nbsp;&nbsp;value string<br>&nbsp;&nbsp;}<br>} | {<br>&nbsp;&nbsp;"clientId": 1234567890<br>&nbsp;&nbsp;"instIdHash": {<br>&nbsp;&nbsp;&nbsp;&nbsp;"method": "sha256"<br>&nbsp;&nbsp;&nbsp;&nbsp;"value": "984271ec70628b47995fdf9271ded6274c2b104ce201164a9b63cfefef7f40d0"<br>&nbsp;&nbsp;}<br>}|
| body | object | {<br>&nbsp;&nbsp;registrationId integer($int64)<br>&nbsp;&nbsp;regHash {<br>&nbsp;&nbsp;&nbsp;&nbsp;method string<br>&nbsp;&nbsp;&nbsp;&nbsp;value string<br>&nbsp;&nbsp;}<br>} | {<br>&nbsp;&nbsp;"registrationId": 1234567890<br>&nbsp;&nbsp;"regHash": {<br>&nbsp;&nbsp;&nbsp;&nbsp;"method": "sha256"<br>&nbsp;&nbsp;&nbsp;&nbsp;"value": "984271ec70628b47995fdf9271ded6274c2b104ce201164a9b63cfefef7f40d0"<br>&nbsp;&nbsp;}<br>}|

Request body type `ClientAuthenticationRequest` defined in [restapi module](../../../pkg/restapi)

## Responses

| Code | Description | Example |
| ---- | ----------- | ------- |
| 200 | Success<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;clientId integer($int64)<br>&nbsp;&nbsp;authToken string<br>&nbsp;&nbsp;registrationDate string<br>} | {<br>&nbsp;&nbsp;"clientId": 1234567890<br>&nbsp;&nbsp;"authToken": "encoded.JWT.token"<br>&nbsp;&nbsp;"registrationDate": "2024-08-01T01:02:03.000000Z"<br>} |
| 400 | Bad Request<br>Missing or incompatible body<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;error string<br>} | {<br>&nbsp;&nbsp;"error": "no clientInstanceId value provided"<br>} |
| 401 | Unauthorized<br>Client (re-)registration required due to one of:<br>- specified client is not registered<br>- invalid clientId provided<br>- provided clientInstanceId hash doesn't match<br>[WWW-Authenticate](../headers/www-authenticate.md) response header will specify recovery action<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;error string<br>} | {<br>&nbsp;&nbsp;"error": "Client not registered"<br>} |
| 200 | Success<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;registrationId integer($int64)<br>&nbsp;&nbsp;authToken string<br>&nbsp;&nbsp;registrationDate string<br>} | {<br>&nbsp;&nbsp;"registrationId": 1234567890<br>&nbsp;&nbsp;"authToken": "encoded.JWT.token"<br>&nbsp;&nbsp;"registrationDate": "2024-08-01T01:02:03.000000Z"<br>} |
| 400 | Bad Request<br>Missing or incompatible body<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;error string<br>} | {<br>&nbsp;&nbsp;"error": "no registration hash value provided"<br>} |
| 401 | Unauthorized<br>Client (re-)registration required due to one of:<br>- specified client is not registered<br>- invalid registrationId provided<br>- provided registration hash doesn't match<br>[WWW-Authenticate](../headers/www-authenticate.md) response header will specify recovery action<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;error string<br>} | {<br>&nbsp;&nbsp;"error": "Client not registered"<br>} |

Response success body type `ClientAuthenticationResponse` defined in [restapi module](../../../pkg/restapi)

Expand Down
8 changes: 4 additions & 4 deletions doc/api/requests/register.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ Type: **POST**

| Name | Type | Description | Example |
| ---- | ---- | ----------- | ------- |
| body | object | {<br>&nbsp;&nbsp;clientInstanceId: string<br>} | {<br>&nbsp;&nbsp;"clientInstanceId": "ba2cb9f4927441602a385b27f502134902b636f395cadb3ea1438084dba29c8c"<br>}|
| body | object | {<br>&nbsp;&nbsp;clientRegistration: {<br>&nbsp;&nbsp;&nbsp;&nbsp;clientId: string<br>&nbsp;&nbsp;&nbsp;&nbsp;systemUUID: string<br>&nbsp;&nbsp;&nbsp;&nbsp;timestamp: string($[rfc3339nano](https://pkg.go.dev/time#pkg-constants))<br>&nbsp;&nbsp;}<br>} | {<br>&nbsp;&nbsp;"clientRegistration": {<br>&nbsp;&nbsp;&nbsp;&nbsp;"clientId": "f323628e-c1cc-45d4-824d-22d4d6f0fd01"<br>&nbsp;&nbsp;&nbsp;&nbsp;"systemUUID": "74f0f0b0-fb29-4405-a0b8-4e7747bdfd8a"<br>&nbsp;&nbsp;&nbsp;&nbsp;"timestamp": "2024-08-01T00:01:02.000000Z"<br>&nbsp;&nbsp;}<br>}|

Request body type `ClientRegistrationRequest` defined in [restapi module](../../../pkg/restapi/)

## Responses

| Code | Description | Example |
| ---- | ----------- | ------- |
| 200 | Success<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;clientId integer($int64)<br>&nbsp;&nbsp;authToken string<br>&nbsp;&nbsp;registrationDate string<br>} | {<br>&nbsp;&nbsp;"clientId": 1234567890<br>&nbsp;&nbsp;"authToken": "encoded.JWT.token"<br>&nbsp;&nbsp;"registrationDate": "2024-08-01T01:02:03.000000Z"<br>} |
| 400 | Bad Request<br>Missing or incompatible body<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;error string<br>} | {<br>&nbsp;&nbsp;"error": "no clientInstanceId value provided"<br>} |
| 409 | Conflict<br>Client Instance Id already registered<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;error string<br>} | {<br>&nbsp;&nbsp;"error": "specified clientInstanceId already exists"<br>} |
| 200 | Success<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;registrationId integer($int64)<br>&nbsp;&nbsp;authToken string<br>&nbsp;&nbsp;registrationDate string<br>} | {<br>&nbsp;&nbsp;"registrationId": 1234567890<br>&nbsp;&nbsp;"authToken": "encoded.JWT.token"<br>&nbsp;&nbsp;"registrationDate": "2024-08-01T01:02:03.000000Z"<br>} |
| 400 | Bad Request<br>Missing or incompatible body<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;error string<br>} | {<br>&nbsp;&nbsp;"error": "missing registration clientId"<br>}<br>or<br>{<br>&nbsp;&nbsp;"error": "missing registration timestamp"<br>} |
| 409 | Conflict<br>Client Registration or Registration's Client Id already registered<br>`Content-Type: application/json`<br>{<br>&nbsp;&nbsp;error string<br>} | {<br>&nbsp;&nbsp;"error": "specified registration already exists"<br>}<br>or<br>{<br>&nbsp;&nbsp;"error": "specified registration clientId already exists"<br>} |

Response success body type `ClientRegistrationResponse` defined in [restapi module](../../../pkg/restapi/)
6 changes: 3 additions & 3 deletions doc/api/structs/telemetrybundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ The TelemetryBundle data structure consists of the following sections:
* bundleTimeStamp - the UTC timestamp for when the bundle was generated,
formatted in [RFC3339nano](../../telemetrytimestamp.md) format
* bundleClientId - the clientId of the telemetry client generating this
bundle, as specified in the credentials returned when the telemetry
client successfully registered with the upstream telemetry server.
bundle, as specified in the registraion used to successfully register
the client with the upstream telemetry server.
* bundleCustomerId - a string value specify the customer identifier,
if any, associated with the telemetry client
* bundleAnnotations - a possibly empty list of
Expand All @@ -25,7 +25,7 @@ in a bundle must originate from the same telemetry client.
header {
bundleId string
bundleTimeStamp string($rfc3339nano)
bundleClientId integer($int64)
bundleClientId string
bundleCustomerId string
bundleAnnotations [
string...
Expand Down
6 changes: 3 additions & 3 deletions doc/api/structs/telemetryreport.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ The TelemetryReport data structure consists of the following sections:
* reportTimeStamp - the UTC timestamp for when the bundle was generated,
formatted in [RFC3339nano](../../telemetrytimestamp.md) format
* reportClientId - the clientId of the telemetry client generating this
report, as specified in the credentials returned when the telemetry
client successfully registered with the upstream telemetry server.
report, as specified in the registraion used to successfully register
the client with the upstream telemetry server.
* reportAnnotations - a possibly empty list of
[telemetry annotation tags](../../telemetrytag.md)
* payload - a list of one or more [TelemetryBundle](telemetrybundle.md) objects
Expand All @@ -24,7 +24,7 @@ relayed through one or more telemetry relays.
header {
reportId string
reportTimeStamp string($rfc3339nano)
reportClientId integer($int64)
reportClientId string
reportAnnotations [
string...
]
Expand Down
12 changes: 0 additions & 12 deletions doc/telemetryclientid.md

This file was deleted.

45 changes: 45 additions & 0 deletions doc/telemetryclientregistration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Telemetry Client Registration

To submit telemetry reports to an upstream telemetry service (gateway
or relay) a client must [register](api/requests/register.md) with that
service. As part of the registration process a client will generate a
client registration structure which is used to uniquely identify it as
a telemetry service client.

## Client Registration Structure
This client registration structure has the following format:
```
{
"clientId": "<client_generated_UUID>",
"systemUUID": "<optional_system_UUID>",
"timestamp": "<time_when_client_id_was_generated>"
}
```

## Client Registration Collisions
A client system may be required to generate a new client registration
if the upstream server detects that the registration is a duplicate
of an existing registration, or that the registration's clientId is
a duplicate of existing client's clientId.

The client registration's clientId value is used to identify the
client generating a [telemetry bundle](api/structs/telemetrybundle.md)
and submitting a [telemetry report](api/structs/telemetryreport.md).

## Uniquely Identifying a client
On its own the clientId may not always uniquely identify a client
within the overall pool of telemetry service submissions, because two
client systems could independently generate the same UUID value to use
as a clientId, but a telemetry client's clientId will always be unique
with respect to other clients of the same upstream telemetry service.

This property, that clientIds will always be unique with respect to
other clients of the same upstream telemetry service, can be leveraged
by [telemetry relays](telemetryrelay.md) to assist in uniquely
identifying telemetry clients. When telemetry is relayed, the relay
will add a RELAYED_VIA tag to the telemetry submission which identifies
both the relay and the client that submitted the telemetry to the
relay. The aggregate of the RELATED_VIA tag values associated with a
rtamalin marked this conversation as resolved.
Show resolved Hide resolved
telemetry data item can thus be used to uniquely identify the path
from the originating client to the main telemetry service gateway,
and thus uniquely identify a specific client's telemetry submissions.
17 changes: 17 additions & 0 deletions doc/telemetryregistrationid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Telemetry Registration Ids

A telemetry server (or relay) manages a pool of telemetry client
registration ids, currently ranging from 1 to MAX_INT64.

Once a client has successfully [registered](api/requests/register.md)
it's [client registration](telemetryclientregistration.md) with an
upstream telemetry service the response will contain the client's
credentials, including the registrationId which will be used to set
the [X-Telemetry-Registration-Id](api/headers/telemetry-registration-id.md)
when submitting telemetry requests to the upstream telemetry service.

Note that this telemetry registration id only has meaning in the
context of a specific combination of telemetry client and telemetry
server, and the same registration id may be assigned to multiple
telemetry clients so long as they are talking to different upstream
telemery servers (or relays).
5 changes: 3 additions & 2 deletions doc/telemetryrelay.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ will be processed as follows:
the [telemetry bundles](api/structs/telemetrybundle.md).
2. The telemetry relay will annotate each bundle with a new
[RELAYED_VIA tag](telemetrytag.md) whose value consists of the
[client Id](telemetryclientid.md) of the client that submitted the report, and the telemetry
relay's own client Id, joined with a `:`.
[registration Id](telemetryregistrationid.md) of the client that
submitted the report, and the telemetry relay's own registration Id,
joined with a `:`.
3. The telemetry relay will stage the received telemetry bundles
locally.
4. Once sufficient bundles are available, or enough time has passed,
Expand Down
14 changes: 7 additions & 7 deletions doc/telemetrytag.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ inheritance rules:

## Telemetry Annotation Tag Examples
Some examples:
* when bundles are relayed via a telemetry relay server a RELAYED_VIA
tag composed of the combination of the client id of the reporting
client and the relay server's client id will be added to the relayed
bundles.
* telemetry relayed via a proxy service may be annotated by the proxy
type, e.g
* when bundles are relayed via a [telemetry relay](telemetryrelay.md)
server a RELAYED_VIA tag composed of the combination of the registration
id of the reporting client and the relay server's registration id will
be added to the relayed bundles.
* telemetry relayed or synthesised via a proxy service could be annotated
by the proxy type, e.g
* PROXY_TYPE=RMT for RMT
* PROXY_TYPE=SUMA for SUSE Manager
* PROXY_TYPE=MLM for Multi Linux Manager
* PROXY_TYPE=SCC for SCC
20 changes: 10 additions & 10 deletions pkg/client/authenticate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import (
// 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) {
// get the registration, failing if it can't be retrieved
regId, err := tc.getRegistration()
if err != nil {
return
}

if err = tc.loadTelemetryAuth(); err != nil {
return fmt.Errorf(
"telemetry client (re-)authentication requires an existing "+
Expand All @@ -23,16 +29,10 @@ func (tc *TelemetryClient) Authenticate() (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"),
RegistrationId: tc.auth.RegistrationId,
RegHash: *regId.Hash("default"),
}

reqBodyJSON, err := json.Marshal(&caReq)
Expand Down Expand Up @@ -92,7 +92,7 @@ func (tc *TelemetryClient) Authenticate() (err error) {
return
}

tc.auth.ClientId = caResp.ClientId
tc.auth.RegistrationId = caResp.RegistrationId
tc.auth.Token = types.TelemetryAuthToken(caResp.AuthToken)
tc.auth.RegistrationDate, err = types.TimeStampFromString(caResp.RegistrationDate)
if err != nil {
Expand All @@ -115,7 +115,7 @@ func (tc *TelemetryClient) Authenticate() (err error) {

slog.Debug(
"successfully authenticated",
slog.Int64("clientId", tc.auth.ClientId),
slog.Int64("registrationId", tc.auth.RegistrationId),
)

return
Expand Down
Loading