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

Device Registry HTTP API #1130

Merged
merged 1 commit into from
May 24, 2019
Merged

Conversation

ctron
Copy link
Contributor

@ctron ctron commented Apr 2, 2019

Documented:

  • Registration API
  • Credentials API
  • Tenant API
  • Authentication

This PR contains an OpenAPI 3 model, which can be rendered and tested using the following command:

# cd hono/site/static/api
docker run -ti --rm -p 8080:8080 -e SWAGGER_JSON=/foo/device-registry.yaml  -v "$(pwd):/foo:z" swaggerapi/swagger-ui

Or directly from GitHub (without the option to make local changes):

docker run -ti --rm -p 8080:8080 -e URL=https://raw.githubusercontent.com/ctron/hono/feature/devreg_api_1/site/static/api/device-registry.yaml swaggerapi/swagger-ui

You can also directly use the hosted version here:

You can actually use the "Try it out" feature, as currently the API describes the "as is" situation. However I would like to change this for the final version. You can already see in the definition why, but let's wait with the discussion until all the current APIs are mapped.

@ctron ctron added this to the 1.0.0 milestone Apr 2, 2019
@ctron ctron force-pushed the feature/devreg_api_1 branch 2 times, most recently from 1738ea2 to 1235e2a Compare April 3, 2019 14:17
@ctron
Copy link
Contributor Author

ctron commented Apr 3, 2019

I added the credentials API, to the best of my understanding.

I also started to work on a revised API, the current process is focused on the basics of tenant + registration. The main changes are:

  • Provide a common error response mechanism
  • Allow versioning data sets
    • GET requests can return a Resource Version
    • PUT requests can provide a Resource Version (from a GET) and the server must return 409 (Conflict) when the current resource version has changed, the client must re-try
  • Data Model
    • Re-use the same object, instead of partially nesting
    • Drop the ID from the payload and put it into the path for create operations
    • TO DISCUSS: Add a few common properties (like enabled), but keep "allow additional"

This is not a final version, but a work in progress in order to discuss and refine it further. Feedback is welcome!

Points to discuss:

  • Versioning the API – My current take on this would be to simply use a base prefix on the URL … e.g. https://foo.bar/v1/, but don't write it down in the spec.
  • The use of "additional properties" – We could allow additional properties on the top level objects (like the current proposal) or require additional properties to go into (e.g.) a data child property. I think there are pros and cons, as always. Having a child property might make it easier to implement a new device registry, while additional properties on the root level might make it easier to support the current state. In any case I think we should define a few "well known" properties, like enabled, that the current Hono components use.
  • … I guess a bunch more

Inspiration came from:

@jbtrystram
Copy link
Contributor

I am not sure about dropping the ID from the payload : having tthe AMQP and the HTTP APIs aligned is a nice feature imo.

@b-abel
Copy link
Contributor

b-abel commented Apr 4, 2019

I like the improvements so far!
One remark regarding the Credentials:
In #871 we added the possibility to let the Device Registry do the hashing of the password (instead of leaving this to the client). So there is an alternative schema of the secret for POST and PUT operations that contains the property "pwd-plain" instead of "hash-function", "pwd-hash" and "salt".

@sophokles73
Copy link
Contributor

In fact, allowing the user to select a (potentially weak) hash function himself looks like a mistake to me in hindsight. I would therefore propose to not allow the user to provide the hashed password anymore at all but require him/her to provide the plain text password instead so that the registry can enforce proper usage of a strong hash function ...

@ctron
Copy link
Contributor Author

ctron commented Apr 4, 2019

In fact, allowing the user to select a (potentially weak) hash function himself looks like a mistake to me in hindsight. I would therefore propose to not allow the user to provide the hashed password anymore at all but require him/her to provide the plain text password instead so that the registry can enforce proper usage of a strong hash function ...

I fully agree. I think the device registry should be configured, by the administrator, to use whatever they think is reasonable. And then let them enforce the use of specific hash algorithm and salt. As right now you can still use an un-salted hash.

@ctron
Copy link
Contributor Author

ctron commented Apr 4, 2019

I am not sure about dropping the ID from the payload : having tthe AMQP and the HTTP APIs aligned is a nice feature imo.

So I think the APIs for AMQP and HTTP service different purposes. And have different behaviors.

For HTTP, you have the ID in the URL/path. Having them, in addition, in the payload only makes things confusing IMHO. Currently the device registry actually removes them before processing internally. So I think it should only live in one play, and for a RESTful API, that sounds like the path to me.

@jbtrystram
Copy link
Contributor

+1 for removing the client-side hash support 👍 Transmitting a hashed password in the payload doesn't add any security anyway.

For HTTP, you have the ID in the URL/path. Having them, in addition, in the payload only makes things confusing IMHO. Currently the device registry actually removes them before processing internally. So I think it should only live in one play, and for a RESTful API, that sounds like the path to me.

you're right, I did not notice the ID was already in the URL, so yeah that make sense to remove it from the payload.

@sophokles73
Copy link
Contributor

Thanks for putting up the swagger UIs 👍 It's really convenient to review the API this way.

POST /tenants/{tenant}

  • It is unclear whether the tenant path variable is required or not. IMHO it should be optional to also allow for implementations to create a (unique) ID. That way, clients would not be required to take part in the guessing game of picking an unused ID.
  • The Location response header should be documented to contain the path to the created object (incl. the potentially created tenant ID).
  • All properties should be optional. The *enabled * property should have a default value of true.

GET /tenants/{tenant}

  • Not sure how a request could be malformed (400), given that it does not contain any payload.
  • I like the idea of returning an object version which clients can use when PUTting an update. However, I wonder why we wouldn't use the standard HTTP ETag header for that purpose.

PUT /tenants/{tenant}

  • I wonder why we wouldn't use the standard HTTP ETag header instead of X-Resource-Version

POST /registrations/{tenant}/{deviceId}

  • Using the plural of noun registration sounds strange to me in this context. What about changing this to devices altogether?
  • IMHO the deviceId parameter should be optional to also allow for implementations to create a (unique) ID. That way, clients would not be required to take part in the guessing game of picking an unused ID.
  • The Location response header should be documented to contain the path to the created object (incl. the potentially created ID).
  • The *enabled * property should be optional and have a default value of true.

GET /registrations/{tenant}/{deviceId}

  • Not sure how a request could be malformed (400), given that it does not contain any payload.
  • Same concerns regarding X-Resource-Version

@ctron
Copy link
Contributor Author

ctron commented Apr 5, 2019

POST /tenants/{tenant}

  • It is unclear whether the tenant path variable is required or not. IMHO it should be optional to also allow for implementations to create a (unique) ID. That way, clients would not be required to take part in the guessing game of picking an unused ID.

Currently it is required. But indeed, it might be a good idea to have an automatically generated tenant ID. If that is what you meant.

GET /tenants/{tenant}, GET /registrations/{tenant}/{deviceId}

  • Not sure how a request could be malformed (400), given that it does not contain any payload.

Yes, there is no payload, but the request could be invalid otherwise. e.g. when we have something like paging, (start > end -> invalid request).

PUT /tenants/{tenant}

  • I wonder why we wouldn't use the standard HTTP ETag header instead of X-Resource-Version

Great idea!

POST /registrations/{tenant}/{deviceId}

  • Using the plural of noun registration sounds strange to me in this context. What about changing this to devices altogether?

The suggestions I read many times is to simply always use the plural form. I kept registration as I wanted to be close the current. But renaming this to "devices" would make absolute sense to me.

@ctron ctron force-pushed the feature/devreg_api_1 branch 3 times, most recently from 371e7b4 to 8b96cd0 Compare April 5, 2019 13:32
@ctron
Copy link
Contributor Author

ctron commented Apr 5, 2019

Ok, I pushed a change. This should cover most of the feedback.

@dejanb
Copy link
Contributor

dejanb commented Apr 5, 2019

I thought a couple of times about changing of registrations to devices and now might be the right time to do it. Hierarchy tenants->devices->credentials look much clearer IMHO

@sophokles73
Copy link
Contributor

Looks good, just one more thing to fix. Clients need to specify the expected version of the resource in a PUT request in the If-Match header, not the ETag header (that is only used to convey the version of the resource in a response).
Accordingly, the status code to return in case the version of the resource to update is not as expected, is 412 (Precondition Failed) and not 409 (Conflict).

@ctron ctron force-pushed the feature/devreg_api_1 branch 2 times, most recently from d1a7991 to b5d42e1 Compare April 8, 2019 06:41
@ctron
Copy link
Contributor Author

ctron commented Apr 8, 2019

I didn't know about If-Match either 😁 … I made the necessary changes. I also added the If-Match to the DELETE operation.

I will move on then to the credentials section.

@sophokles73
Copy link
Contributor

I also added the If-Match to the DELETE operation.

So, you would only want to delete the object if its version was A but you would not want to delete it if its version was B? I wonder what the use case for that would be ....

@ctron
Copy link
Contributor Author

ctron commented Apr 8, 2019

So, you would only want to delete the object if its version was A but you would not want to delete it if its version was B? I wonder what the use case for that would be ....

Correct.

The reason for this would be to detect/prevent and accidental deletion of a re-created device. Assuming two operations in parallel:

a) delete device "A", create device "A"
b) get device "A", make decision to delete, delete device "A"

Depending on the order of operations the outcome might be different each time. And the intention of deleting "A" in b) would be based on the original "A", not the re-created "A".

If the user doesn't care in b), then the header parameter can simply be omitted and the re-created A gets deleted, but if the user cares, then there would be a way now.

@Christian-Schmid
Copy link
Contributor

Hi,

thank you very much for the API draft. Having a well defined Hono rest API is something I always wanted :-)

One of the big benefits for such I see for writing web UIs for device / tenant management.

For such operations one method however I sill would wish to be well-defined as-well: A list operation on the devices (and probably for the tenants). I already implemented such method as a draft for our hono deployment (with pagination / querying). If you're interested I could post our signature here.

Thanks
Chris

@ctron
Copy link
Contributor Author

ctron commented Apr 8, 2019

A list operation on the devices (and probably for the tenants)

Yes, I think that is a good idea. If you have something already, I would be glad to take a look at this.

@Christian-Schmid
Copy link
Contributor

Hi,

I'm currently a bit busy, so here the api draft just as text (and not as a nice openapi doc):

GET call on "/registration/<tenantId>"

Query Parameters:

  • q: Perfoms a starts-with based search on the device-id
  • limit: Limits the maximum number of returned results per page (e.g. default with 25 and a maximum of 100)
  • skip : The number of entries to "skip" in the result. Used to page through the result set.

Example:
GET /registration/<tenantId>?q=dummy-dev&limit=25&skip=0

Response:

{
   total: 2 
   "registrations": [
        {
            "enabled": true,
            "device-id": "dummy-dev-1"
        },
        {
            "enabled": true,
            "device-id": "dummy-dev-2"
        }
    ]
}

@sophokles73
Copy link
Contributor

Yes, I think that is a good idea.

I am a little more reluctant than @ctron. The cons listed in the provided link pose some interesting questions. Do we expect to impose some particular order for the objects, e.g. based on ID? That would probably alleviate the issue a little. And before the question comes up: I am quite strictly opposed to having a query API in addition to get by ID .... :-)

$ref: '#/components/responses/Unauthorized'
403:
$ref: '#/components/responses/NotAllowed'
409:
Copy link
Contributor

@jbtrystram jbtrystram May 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if no read access it should answer 404 as well.. Having 403 to mask conflict and 404 to mask 403 allows someone to poke around and find outs existing tenants ID, if i am correct.

- tenants
summary: Create new tenant
operationId: createTenantWithId
parameters:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even thought this naming is valid in terms of rest and openapi in general, its problematic for most of the open-api code generators(at least the java once). Since both, the path-parameter and the body are called "tenant", the generated interfaces would have duplicate variable names in the updateTenant and createTenantWithId methods.
I would suggest to call the pathParameter something like tenantName or tenantId (similar to deviceId in the DevicesApi) to avoid such problems.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting point, I just tried that:

$ npx @openapitools/openapi-generator-cli generate -i device-registry-revised.yaml -g java -o build/
npx: installed 1 in 1.252s
Exception in thread "main" org.openapitools.codegen.SpecValidationException: There were issues with the specification. The option can be disabled via validateSpec (Maven/Gradle) or --skip-validate-spec (CLI).
 | Error count: 9, Warning count: 0
Errors: 
	-attribute paths.'/devices/{tenant}/{deviceId}'(put).parameters.There are duplicate parameter values
	-attribute paths.'/credentials/{tenant}/{deviceId}'(get).parameters.There are duplicate parameter values
	-attribute paths.'/devices/{tenant}/{deviceId}'(delete).parameters.There are duplicate parameter values
	-attribute paths.'/devices/{tenant}/{deviceId}'(get).parameters.There are duplicate parameter values
	-attribute paths.'/credentials/{tenant}/{deviceId}'(put).parameters.There are duplicate parameter values
	-attribute paths.'/credentials/{tenant}/{deviceId}'(delete).parameters.There are duplicate parameter values
	-attribute paths.'/tenants/{tenant}'(put).parameters.There are duplicate parameter values
	-attribute paths.'/tenants/{tenant}'(delete).parameters.There are duplicate parameter values
	-attribute paths.'/devices/{tenant}/{deviceId}'(post).parameters.There are duplicate parameter values

	at org.openapitools.codegen.config.CodegenConfigurator.toClientOptInput(CodegenConfigurator.java:626)
	at org.openapitools.codegen.cmd.Generate.run(Generate.java:367)
	at org.openapitools.codegen.OpenAPIGenerator.main(OpenAPIGenerator.java:60)

So that is indeed something that needs fixing. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I did clean up the parameter definitions. However I think the remaining validation errors are due to:

However that shouldn't be a real problem. I will check the body issue now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see your point now. Code generation is always fun 🙄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed now.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, looks good now.:)

"psk": '#/components/schemas/PSKSecret'
"x509-cert": '#/components/schemas/X509CertificateSecret'

CommonSecret:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm missing something, but I think the CommonSecret should extend the TypedSecret.
E.g.:

CommonSecret:
      allOf:
        - $ref: '#/components/schemas/TypedSecret'
        - type: object
          properties:
            "type":
              type: string
            "enabled":
              type: boolean
              default: true
            "not-before":
              type: string
              format: date-time
            "not-after":
              type: string
              format: date-time
            "comment":
              type: string

Again, in terms of code generation for java, this would lead to a decoupled TypedSecret and would be hard to get back together with the concrete secrets. Currently you would get something like:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = PasswordSecret.class, name = "hashed-password"),
  @JsonSubTypes.Type(value = PSKSecret.class, name = "psk"),
  @JsonSubTypes.Type(value = X509CertificateSecret.class, name = "x509-cert"),
})
public class TypedSecret { ... }

public class CommonSecret { ... }

public class X509CertificateSecret extends CommonSecret { ... }

where the "oneOf" information is (in best case) included as annotations, but not in the object hirarchy. The example is for spring(with jackson), if you use the plain java generator (or f.e. the vertx-generator) you get no connection between TypedSecret and Common/PasswordSecret/etc. at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to explain this in previous comment. The idea was the following (and I didn't check for the generated code, but then again, code generation always misses something):

Each device has a list of authentication identities, which is defined as a Map<String,AuthenticationIdentity>. Each AuthenticationIdentity has a list of secrets, which could either be PasswordSecret, PSKSecret, or some other type.

So the AuthenticationIdentity should look like:

class AuthenticationIdentity {
  List<TypedSecret> secrets;
}

The TypedSecret is the "union type" for a single secret entry. It is required to have type, but none of the other properties from CommonSecret.

The other secrets however share the fields from the CommonSecret, just because the choose to do so.

So again, looking at Java I would see this as:

interface TypedSecret {
   String getType();
   String setType(String type);
}

class CommonSecret {
   public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }
  // ...
}

class PassswordSecret extends CommonSecret {
}

My experience with the code generators from OpenAPI and Swagger showed that you will always have something that doesn't really work. And if it works fine in language A, it fails in language B. So, I am not sure how far we should push the spec file to produce code. I would see it more as a documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But just to be clear. I am open to suggestions on how to improve this!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. It seems that this is a shortcoming of the generators, it makes totally sense the way you defined it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wistefan I am glad to hear that. I did in the meantime play around with the model, based on your proposal and what OpenAPI documents. I ended up with something that worked fine-ish in Java, was messed up both in Go and the actual Specification then (and I didn't test beyond that).

I guess we have to still wait a few centuries, for a modelling approach that just works 😉

So for the moment I would focus in the spec itself, and less on the code generation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And just because I didn't say it explicitly. I am thankful for your input! Keep it coming!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Especially for java, there are quite some discussions on how to implement the support for one-of(and for that matter all-of and any-of) in the generators. I extended my generator now to produce it as:

interface TypedSecret {
   String getType();
   String setType(String type);
}

class CommonSecret {
   public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }
  // ...
}

class PassswordSecret extends CommonSecret implements TypedSecret {
}

which seems to work best at the moment.
I also think it will take a while to for a working modelling approach(if there will be one at all).

@ctron
Copy link
Contributor Author

ctron commented May 10, 2019

I added a note about the non-focus on code generators. If you think that is too grumpy, just let me know.


# Common schema

Error:
Copy link

@wistefan wistefan May 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you already define a specific error response object, I think it would be good to include a (optional) error-code field. A frontend might use it to show localized messages or an automated client might use it for a more specific errorhandling.

Beside that, I mostly finished an implementation of this api and I really like it👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wistefan which parts did you implement ? I wanted to start this work during this week but there is no need to duplicate the effort if you've worked on it. Would you like some help ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole Rest-Part of the api, including persistence in a sql-database. Since I implemented it using micronaut I'm not totally sure if it will fit into the whole hono project and I still have some work to do until I can put it on github(f.e. resolving the dependencies to our internal openapi-generator). It will probably take a couple of weeks(~4) until I can prepare a PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you did not update the HttpEndpoint classes ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.

@ctron
Copy link
Contributor Author

ctron commented May 15, 2019

@sophokles73 I made the change requested by you of putting the "auth-id" explicitly into the secrets as a field. Please have a look if that is ok for you now.

@jbtrystram
Copy link
Contributor

jbtrystram commented May 15, 2019

I've started to work on implementing this in #1240 .

I noticed two things on the tenant API :

  • If-match headers for resource version are missing in the GET and POST operations for tenants. Is it intentionnal ? If yes, what's the point of creating versionned resources if I cannot request them afterwards ?
  • I expect this versionning feature to be optionnal. If a GET request comes in with a given tenantID without a version specified, and several versions exist for this value. Which one should be used in the response ?
  • I think there is a security issue in the conflict / no read acess response : a non authorized user receive 403 Unauthorized insead of 409 Conflict when trying to _ POST_ (add operation) a value while having no read access. That means I can discover the existing tenantID values by poking around even i I have no read access : i'll get 403 Unauthorized instead of 404 Not found, hence knowing the tenantID I just tried exists.

@ctron
Copy link
Contributor Author

ctron commented May 16, 2019

  • If-match headers for resource version are missing in the GET and POST operations for tenants. Is it intentionnal ? If yes, what's the point of creating versionned resources if I cannot request them afterwards ?

For the GETmethod yes. You always get the most recent version. But you also, in the response headers (the etag), get the version you can use for modifying operations.

  • I expect this versionning feature to be optionnal. If a GET request comes in with a given tenantID without a version specified, and several versions exist for this value. Which one should be used in the response ?

The versioning is indeed optional. If you don't provide it in a modifying operation, the version check will be skipped.

  • I think there is a security issue in the conflict / no read acess response : a non authorized user receive 403 Unauthorized insead of 409 Conflict when trying to _ POST_ (add operation) a value while having no read access. That means I can discover the existing tenantID values by poking around even i I have no read access : i'll get 403 Unauthorized instead of 404 Not found, hence knowing the tenantID I just tried exists.

Looks like it, I will check again.

@ctron
Copy link
Contributor Author

ctron commented May 16, 2019

  • I think there is a security issue in the conflict / no read acess response : a non authorized user receive 403 Unauthorized insead of 409 Conflict when trying to _ POST_ (add operation) a value while having no read access. That means I can discover the existing tenantID values by poking around even i I have no read access : i'll get 403 Unauthorized instead of 404 Not found, hence knowing the tenantID I just tried exists.

Looks like it, I will check again.

After reading through it once again, I think it is complicated 😁

So yes, the scenario you described would give out the information if the tenant exists or not. I was assuming that the user either has "create tenant" access, in which case you would get 409 or 201, depending on the actual outcome. And in the case of "not having create tenant access", you would only get 409 if he also has "read this tenant" access. If he hasn't, he would get 403, like he would get when trying to create any other tenant (as he doesn't have "create tenant" access).

Now you could have the situation that a user has "create tenant" access, but doesn't have access for this specific tenant. Honestly I don't know what to do in that case, because the tenant name is a unique element, and you simply have to reject the request.

On the other hand I think it is fair to say that, if the user has a global "create tenant" access role, then he can also know if that specific tenant exists or not. He still woulnd't see the content of that tenant though.

@jbtrystram
Copy link
Contributor

jbtrystram commented May 16, 2019

Thanks for your answers @ctron, that clears a lot of things for me 👍

On the other hand I think it is fair to say that, if the user has a global "create tenant" access role, then he can also know if that specific tenant exists or not. He still woulnd't see the content of that tenant though.

That sounds like a reasonable asumption to make. Maybe that could be enforced in the auth service ? i.e. having a hierarchy of roles and one cannot have create access without read, so such situations are avoided (unix permission model works great for that)

properties:
"type":
type: string
"not-before":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do understand your intention but I strongly disagree with the approach taken.

  1. The auth-id is actually one of the most important properties when it comes to authenticating a device. The fact that the schema does not define nor mention it in any place, makes it close to impossible to understand the structure of the data without reading prose in the form of comments (which are lacking).
  2. The authentication identifier may be shared by multiple secrets but it does not have to be shared. Thus, using it as the key in the secrets map seems arbitrary to me. In fact, devices will most probably not have dozens of secrets defined for them but will more likely have 1 to 5 of them. Extracting the auth-id in order to allow for quicker look up (if that is indeed the motivation) will probably not make much difference. However, not having an explicit auth-id property within the secret, makes the whole thing harder to read and understand FMPOV.

I would therefore much prefer to have the CommonSecret explicitly define the auth-id property and refrain from using the auth-id implicitly as the key in a map of secrets, but instead simply contain the secrets in an array of CommonSecrets ...

title: Eclipse Hono™ Device Registry API
description: |
This API allows one to interact with the Eclipse Hono Device Registry
API. It acts as a common basis which all Hono device registries should
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no such thing as the Device Registry API. Instead, we have Tenant, Device Registration and Credentials APIs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed wording.

the specification in a way to please code generators.

contact:
email: "hono-dev@eclipse.org"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems a little problematic, as you can only send to this address, if you have subscribed to the list ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am open to suggestions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would replace the email property with a url property pointing to https://www.eclipse.org/hono/community/get-in-touch/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

site/static/api/device-registry-v1.yaml Outdated Show resolved Hide resolved
site/static/api/device-registry-v1.yaml Show resolved Hide resolved
site/static/api/device-registry-v1.yaml Show resolved Hide resolved
site/static/api/device-registry-v1.yaml Show resolved Hide resolved
"comment":
type: string

X509CertificateSecret:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this secret have additional properties?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not to my knowledge. However it is a distinct type via the "discriminator" value in TypedSecret.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering, if the way that the object is defined, would allow clients to set additional properties on it ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, now I understand what you meant. Done.

allOf:
- $ref: '#/components/schemas/CommonSecret'

PasswordSecret:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this secret have additional properties?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not to my knowledge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

type: string
format: byte

PSKSecret:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this secret have additional properties?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not to my knowledge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

@ctron ctron force-pushed the feature/devreg_api_1 branch 2 times, most recently from 3cfd6d2 to a6e1644 Compare May 20, 2019 12:25
Copy link
Contributor

@sophokles73 sophokles73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW I haven't taken a look at the spec for the existing API. Do you really want to add it as well?

401:
$ref: '#/components/responses/Unauthorized'
403:
$ref: '#/components/responses/NotAllowed'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I see.

"comment":
type: string

X509CertificateSecret:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering, if the way that the object is defined, would allow clients to set additional properties on it ...

@ctron ctron force-pushed the feature/devreg_api_1 branch 2 times, most recently from 7961987 to a6b5d78 Compare May 20, 2019 13:33
@ctron
Copy link
Contributor Author

ctron commented May 20, 2019

BTW I haven't taken a look at the spec for the existing API. Do you really want to add it as well?

No, before merging, I would like to drop the "old" API spec, and squash the PR. I would still recommend to keep the existing API implemented for a release or two. Just to let people continue to work with existing documentation and not break those.

@ctron ctron force-pushed the feature/devreg_api_1 branch 2 times, most recently from 037529e to b14679f Compare May 20, 2019 13:41
Copy link
Contributor

@sophokles73 sophokles73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

put:
tags:
- credentials
summary: Update credentials set for registered device
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only way to add a credentials seems to be getting all credentials, then modifying, deleting or adding an entry and then putting the result back. This requires some client side logic for JSON parsing to find the right entry. Dedicated URLS for putting and deleting an entry would add much more convenience for the client. A use case like credential rotation can then be implemented without any parsing.

PUT /credentials/{tenantId}/{deviceId}/{auth-id}
DELETE /credentials/{tenantId}/{deviceId}/{auth-id}

Updating a set at once (PUT /credentials/{tenantId}/{deviceId}}) may not be needed when individual URLs are provided.
Deleting all at once would still be convenient for cleaning up all credentials at once.

Was the option of providing dedicated auth id update considered?
This is just an API consumers view, there might be some reasons internally, why this isn't a good idea :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense to me. However, the resources would need to include the credentials type as well, i.e.

PUT /credentials/{tenantId}/{deviceId}/{type}/{auth-id}
DELETE /credentials/{tenantId}/{deviceId}/{type}/{auth-id}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the option of providing dedicated auth id update considered?

However, the resources would need to include the credentials type

That is how the current API look like : https://current-api-hono-device-registry-api.wonderful.iot-playground.org/
I am not sure why those operation have been removed. Also : how does one create a credential if none exist yet ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I touched that point in the following comment: #1130 (comment)

I agree, I think it makes sense to have such specialized operations. However they can all be implemented on top of this API. And I think it makes sense to add them in a later version of the API. However I don't think it conflicts with the current API definition.

Assuming you have all kinds of specialized secrets (Password, PSK, client cert, custom protocol adapter secrets) I think it makes sense to support them in the way currently described. And then adding more specific operations on top of it, like:

  • Cleanup expired passwords
  • Password / Key rollover

So, let's finish this first. And then do version 1.1, including search and specialized secret operations.

This change adds an OpenAPI v3 definition of the device registry API,
used for managing tenants, devices and credentials. It is an addition
to the existing AMQP 1.0 API, but is intended to be used to provide
common tooling for managing devices in a Hono compatible device
registry.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
1.0-M4
  
Done
Development

Successfully merging this pull request may close these issues.

None yet

9 participants