-
Notifications
You must be signed in to change notification settings - Fork 31
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
feat(discovery): JWT flow #1059
feat(discovery): JWT flow #1059
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything looks good to me.
- I'm just wondering if the reason we are using HS256 over something like RSA256 is because cryostat is both the verifier and the provider of the token so there's no need to?
- Also, what was the
COMMANDS.md
file and why was it removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I seem to get some problems when running sh smoketest.sh postgres
with the pr.
Sep 02, 2022 11:38:01 PM io.cryostat.core.log.Logger error
SEVERE: HTTP 500: Cannot invoke "String.trim()" because "s" is null
io.vertx.ext.web.handler.HttpException: Internal Server Error
Caused by: io.cryostat.net.web.http.api.v2.ApiException: Cannot invoke "String.trim()" because "s" is null
at io.cryostat.net.web.http.api.v2.AbstractDiscoveryJwtConsumingHandler.handle(AbstractDiscoveryJwtConsumingHandler.java:114)
at io.cryostat.net.web.http.api.v2.AbstractDiscoveryJwtConsumingHandler.handle(AbstractDiscoveryJwtConsumingHandler.java:77)
at io.vertx.ext.web.impl.BlockingHandlerDecorator.lambda$handle$0(BlockingHandlerDecorator.java:48)
at io.vertx.core.impl.ContextImpl.lambda$null$0(ContextImpl.java:159)
at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$1(ContextImpl.java:157)
at io.vertx.core.impl.TaskQueue.run(TaskQueue.java:76)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.NullPointerException: Cannot invoke "String.trim()" because "s" is null
at com.nimbusds.jose.JOSEObject.split(JOSEObject.java:218)
at com.nimbusds.jose.JWEObject.parse(JWEObject.java:502)
at io.cryostat.net.security.jwt.DiscoveryJwtHelper.parseDiscoveryPluginJwt(DiscoveryJwtHelper.java:150)
at io.cryostat.net.security.jwt.DiscoveryJwtHelper.parseDiscoveryPluginJwt(DiscoveryJwtHelper.java:139)
at io.cryostat.net.web.http.api.v2.AbstractDiscoveryJwtConsumingHandler.validateJwt(AbstractDiscoveryJwtConsumingHandler.java:147)
at io.cryostat.net.web.http.api.v2.AbstractDiscoveryJwtConsumingHandler.handle(AbstractDiscoveryJwtConsumingHandler.java:108)
... 10 more
Sep 02, 2022 11:38:01 PM io.cryostat.core.log.Logger info
INFO: (10.0.2.100:54004): POST /api/v2.2/discovery/c3c3c16b-060b-41db-8cf8-dbdafafe298d 500 3ms
Hibernate:
/* insert io.cryostat.discovery.PluginInfo
*/ insert
into
PluginInfo
(callback, realm, subtree, id)
values
(?, ?, ?, ?)
Sep 02, 2022 11:38:03 PM io.cryostat.core.log.Logger info
INFO: (10.0.2.100:33052): POST /api/v2.2/discovery 201 4ms
Hibernate:
select
plugininfo0_.id as id1_0_0_,
plugininfo0_.callback as callback2_0_0_,
plugininfo0_.realm as realm3_0_0_,
plugininfo0_.subtree as subtree4_0_0_
from
PluginInfo plugininfo0_
where
plugininfo0_.id=?
Sep 02, 2022 11:38:03 PM io.cryostat.core.log.Logger error
SEVERE: HTTP 500: Cannot invoke "String.trim()" because "s" is null
io.vertx.ext.web.handler.HttpException: Internal Server Error
Caused by: io.cryostat.net.web.http.api.v2.ApiException: Cannot invoke "String.trim()" because "s" is null
at io.cryostat.net.web.http.api.v2.AbstractDiscoveryJwtConsumingHandler.handle(AbstractDiscoveryJwtConsumingHandler.java:114)
at io.cryostat.net.web.http.api.v2.AbstractDiscoveryJwtConsumingHandler.handle(AbstractDiscoveryJwtConsumingHandler.java:77)
at io.vertx.ext.web.impl.BlockingHandlerDecorator.lambda$handle$0(BlockingHandlerDecorator.java:48)
at io.vertx.core.impl.ContextImpl.lambda$null$0(ContextImpl.java:159)
at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$1(ContextImpl.java:157)
at io.vertx.core.impl.TaskQueue.run(TaskQueue.java:76)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.NullPointerException: Cannot invoke "String.trim()" because "s" is null
at com.nimbusds.jose.JOSEObject.split(JOSEObject.java:218)
at com.nimbusds.jose.JWEObject.parse(JWEObject.java:502)
at io.cryostat.net.security.jwt.DiscoveryJwtHelper.parseDiscoveryPluginJwt(DiscoveryJwtHelper.java:150)
at io.cryostat.net.security.jwt.DiscoveryJwtHelper.parseDiscoveryPluginJwt(DiscoveryJwtHelper.java:139)
at io.cryostat.net.web.http.api.v2.AbstractDiscoveryJwtConsumingHandler.validateJwt(AbstractDiscoveryJwtConsumingHandler.java:147)
at io.cryostat.net.web.http.api.v2.AbstractDiscoveryJwtConsumingHandler.handle(AbstractDiscoveryJwtConsumingHandler.java:108)
... 10 more
Sep 02, 2022 11:38:03 PM io.cryostat.core.log.Logger info
INFO: (10.0.2.100:33052): POST /api/v2.2/discovery/c73a2fb2-a742-4ab4-988f-2692a10a0940 500 2ms
Hibernate:
/* insert io.cryostat.discovery.PluginInfo
*/ insert
into
PluginInfo
(callback, realm, subtree, id)
values
(?, ?, ?, ?)
Sep 02, 2022 11:38:05 PM io.cryostat.core.log.Logger info
INFO: (10.0.2.100:33056): POST /api/v2.2/discovery 201 5ms
Hibernate:
select
plugininfo0_.id as id1_0_0_,
plugininfo0_.callback as callback2_0_0_,
plugininfo0_.realm as realm3_0_0_,
plugininfo0_.subtree as subtree4_0_0_
from
PluginInfo plugininfo0_
where
plugininfo0_.id=?
This pattern of messages pops up every 2 seconds after logging into the web-client and a different POST id is shown every time.
INFO: (10.0.2.100:33052): POST /api/v2.2/discovery/c73a2fb2-a742-4ab4-988f-2692a10a0940 500 2ms
...
INFO: (10.0.2.100:33056): POST /api/v2.2/discovery/3e475042-0727-4ba0-8357-5175a62b7b88 500 2ms
When I run with h2file
I get a different error every 2 seconds:
Hibernate:
/* insert io.cryostat.discovery.PluginInfo
*/ insert
into
PluginInfo
(callback, realm, subtree, id)
values
(?, ?, ?, ?)
Sep 02, 2022 11:42:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
WARN: SQL Error: 23505, SQLState: 23505
Sep 02, 2022 11:42:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Unique index or primary key violation: "UK_LFC8YJ7NOT9MTD4XYCNE72QEC_INDEX_5 ON PUBLIC.PLUGININFO(CALLBACK) VALUES ('http://cryostat:10010/cryostat-discovery', 4)"; SQL statement:
/* insert io.cryostat.discovery.PluginInfo */ insert into PluginInfo (callback, realm, subtree, id) values (?, ?, ?, ?) [23505-197]
Sep 02, 2022 11:42:09 PM org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl release
INFO: HHH000010: On release of batch it still contained JDBC statements
Sep 02, 2022 11:42:09 PM io.cryostat.core.log.Logger error
SEVERE: Exception thrown
javax.persistence.RollbackException: Error while committing the transaction
at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:81)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104)
at io.cryostat.storage.AbstractDao.save(AbstractDao.java:71)
at io.cryostat.discovery.PluginInfoDao.save(PluginInfoDao.java:74)
at io.cryostat.discovery.DiscoveryStorage.register(DiscoveryStorage.java:160)
at io.cryostat.net.web.http.api.v2.DiscoveryRegistrationHandler.handle(DiscoveryRegistrationHandler.java:155)
at io.cryostat.net.web.http.api.v2.AbstractV2RequestHandler.handle(AbstractV2RequestHandler.java:123)
at io.cryostat.net.web.http.api.v2.AbstractV2RequestHandler.handle(AbstractV2RequestHandler.java:75)
at io.vertx.ext.web.impl.BlockingHandlerDecorator.lambda$handle$0(BlockingHandlerDecorator.java:48)
at io.vertx.core.impl.ContextImpl.lambda$null$0(ContextImpl.java:159)
at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$1(ContextImpl.java:157)
at io.vertx.core.impl.TaskQueue.run(TaskQueue.java:76)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:65)
... 16 more
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:109)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:37)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200)
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:46)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3375)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3908)
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:107)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:721)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:344)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1407)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:489)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3290)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2425)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:449)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
... 15 more
Caused by: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "UK_LFC8YJ7NOT9MTD4XYCNE72QEC_INDEX_5 ON PUBLIC.PLUGININFO(CALLBACK) VALUES ('http://cryostat:10010/cryostat-discovery', 4)"; SQL statement:
/* insert io.cryostat.discovery.PluginInfo */ insert into PluginInfo (callback, realm, subtree, id) values (?, ?, ?, ?) [23505-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.index.BaseIndex.getDuplicateKeyException(BaseIndex.java:101)
at org.h2.mvstore.db.MVSecondaryIndex.requireUnique(MVSecondaryIndex.java:236)
at org.h2.mvstore.db.MVSecondaryIndex.add(MVSecondaryIndex.java:202)
at org.h2.mvstore.db.MVTable.addRow(MVTable.java:732)
at org.h2.command.dml.Insert.insertRows(Insert.java:182)
at org.h2.command.dml.Insert.update(Insert.java:134)
at org.h2.command.CommandContainer.update(CommandContainer.java:102)
at org.h2.command.Command.executeUpdate(Command.java:261)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:199)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:153)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
... 35 more
Sep 02, 2022 11:42:09 PM io.cryostat.core.log.Logger warn
WARNING: HTTP 400: Failed to register new provider for "quarkus-test-ac4c4c7c-dbd0-4c8e-ada4-764317fef7af" @ "http://cryostat:10010/cryostat-discovery": provider already present
io.vertx.ext.web.handler.HttpException: Bad Request
Caused by: io.cryostat.net.web.http.api.v2.ApiException: Failed to register new provider for "quarkus-test-ac4c4c7c-dbd0-4c8e-ada4-764317fef7af" @ "http://cryostat:10010/cryostat-discovery": provider already present
at io.cryostat.net.web.http.api.v2.DiscoveryRegistrationHandler.handle(DiscoveryRegistrationHandler.java:157)
at io.cryostat.net.web.http.api.v2.AbstractV2RequestHandler.handle(AbstractV2RequestHandler.java:123)
at io.cryostat.net.web.http.api.v2.AbstractV2RequestHandler.handle(AbstractV2RequestHandler.java:75)
at io.vertx.ext.web.impl.BlockingHandlerDecorator.lambda$handle$0(BlockingHandlerDecorator.java:48)
at io.vertx.core.impl.ContextImpl.lambda$null$0(ContextImpl.java:159)
at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$1(ContextImpl.java:157)
at io.vertx.core.impl.TaskQueue.run(TaskQueue.java:76)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: io.cryostat.discovery.RegistrationException: Failed to register new provider for "quarkus-test-ac4c4c7c-dbd0-4c8e-ada4-764317fef7af" @ "http://cryostat:10010/cryostat-discovery": provider already present
at io.cryostat.discovery.DiscoveryStorage.register(DiscoveryStorage.java:165)
at io.cryostat.net.web.http.api.v2.DiscoveryRegistrationHandler.handle(DiscoveryRegistrationHandler.java:155)
... 11 more
Caused by: javax.persistence.RollbackException: Error while committing the transaction
at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:81)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104)
at io.cryostat.storage.AbstractDao.save(AbstractDao.java:71)
at io.cryostat.discovery.PluginInfoDao.save(PluginInfoDao.java:74)
at io.cryostat.discovery.DiscoveryStorage.register(DiscoveryStorage.java:160)
... 12 more
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:65)
... 16 more
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:109)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:37)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200)
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:46)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3375)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3908)
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:107)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:721)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:344)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1407)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:489)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3290)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2425)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:449)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
... 15 more
Caused by: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "UK_LFC8YJ7NOT9MTD4XYCNE72QEC_INDEX_5 ON PUBLIC.PLUGININFO(CALLBACK) VALUES ('http://cryostat:10010/cryostat-discovery', 4)"; SQL statement:
/* insert io.cryostat.discovery.PluginInfo */ insert into PluginInfo (callback, realm, subtree, id) values (?, ?, ?, ?) [23505-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.index.BaseIndex.getDuplicateKeyException(BaseIndex.java:101)
at org.h2.mvstore.db.MVSecondaryIndex.requireUnique(MVSecondaryIndex.java:236)
at org.h2.mvstore.db.MVSecondaryIndex.add(MVSecondaryIndex.java:202)
at org.h2.mvstore.db.MVTable.addRow(MVTable.java:732)
at org.h2.command.dml.Insert.insertRows(Insert.java:182)
at org.h2.command.dml.Insert.update(Insert.java:134)
at org.h2.command.CommandContainer.update(CommandContainer.java:102)
at org.h2.command.Command.executeUpdate(Command.java:261)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:199)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:153)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
... 35 more
Sep 02, 2022 11:42:09 PM io.cryostat.core.log.Logger info
INFO: (10.0.2.100:49182): POST /api/v2.2/discovery 400 5ms
I'll try to reproduce those failures, thanks for detailing them.
Right, HS256 is used because it's symmetric - there is just one key and it is randomly generated each time a Cryostat instance is started. Since Cryostat is both the creator and consumer of the token and we don't need or want any other client that sees the token to be able to read it this works well.
|
Ah, right. The Do you have any feedback or questions about the JWT additions at this point? |
e090840
to
2e5b044
Compare
Thanks for the clarification on my previous questions!
I guess I'm wondering what JWT authentication provides in addition to the authentication cryostat already had beforehand (e.g. the Also, not really related to this PR but cryostat JWT in general, when does it make sense to include JWT auth vs. not to in an endpoint? For example, v2 |
The older set of v2 JWT-based handlers, like the These work by allowing the web-client running in the browser to perform an API request with our normal Authorization header flow, where the client making the request also specifies what resource they want to download. Cryostat performs an auth check on the Authorization header and if this passes it generates a token to send back to the client. This token is encrypted and signed by Cryostat itself so that to the receiving client it's just an opaque string of characters with no discernible meaning, and any modifications to the text will invalidate the token (signature failure). The client can then make a v2 API request to download the resource it actually wants (ex. a report) and include the token as a query parameter in the request without having to set any request headers. Cryostat then receives that token, checks the signature and decrypts it - if these steps succeed then we know that the token was generated by the current Cryostat instance and was not subsequently modified. The token also contains information about who the requesting client is (the Authorization header they originally passed, which hooks back into the All of this is to say that the original JWT handlers are there essentially just to make the web-client work better, so that we can use the standard browser file download functionality. Originally that functionality was hacked around by having the web-client make an Authorization-header request to the v1 API endpoints and download the response fully into browser memory as a Blob, then save that to file after the download completed. This meant the user never had the chance to cancel a request or cancel an in-progress download, and the web-client couldn't be closed without interrupting the download either. Right now we still have this distinct split between using the Authorization header vs using JWT, but I hope that in a future major release version (3.0?) we can unify this and either adopt JWT for everything or have an implementation that allows either to be used interchangeably depending on the client's capabilities. The downside to using JWT for everything is that it forces the client to make a request for a JWT and then follow up with the actual request they wanted to make, so it's more resource intensive and is subject to double the network latency. For that reason, JWT is only really used for file GET/download requests that might be initiated by the web-client. Everything else uses the standard Authorization header style. |
In the context of this PR and the use of JWT w.r.t. the Pluggable Discovery API, many of the same concepts as in my previous response are still valid. The plugins register themselves using a standard request with an Authorization header, at which point a JWT is generated and sent back. Subsequent requests for publishing discovery subtrees or for deregistering use the JWT rather than the Authorization header. The JWT is again signed and encrypted, and this time it contains information about the plugin's discovery subtree Realm. In practical terms this means that any client that can pass an Authorization check can register itself as a discovery plugin. But, once such a registration occurs for a given Realm name, then only a client bearing a JWT for that Realm is able to perform deregistrations or to publish subtrees for that Realm. It essentially allows Cryostat to set a sub scope of authorization for the subtree Realm to only one client at a time. The current implementation before this PR using only the Authorization header allows for any client to theoretically publish subtree updates to any Realm, or even to deregister other plugins and delete their Realms. The only mitigation against this prior to this PR is that the plugin registration uses a UUID instead of a simple incrementing numeric ID, so the IDs are very difficult to predict. But if you do guess a valid registration UUID and can pass an Authorization check then you can freely publish discovery updates to that plugin's Realm or deregister the plugin. The IDs are still UUIDs and the JWTs expire fairly quickly, so the only way a client can publish subtree updates or deregister is to both know the correct plugin UUID as well as have a current JWT, both of which aren't going to happen by guessing or by accident. |
2e5b044
to
eadd109
Compare
On discovery plugin registration, server generates a JWT and supplies it to the plugin. This JWT must be supplied by the plugin in place of Authorization header for discovery updates and deregistration. Server's existing periodic ping task to check plugin reachability should prompt plugin to attempt to re-register itself, but this time it has an existing valid token to supply. If the server gets a registration request that includes a refreshed token is generated and supplied back into the same registration request flow.
eadd109
to
6bcb816
Compare
startup ping is a GET, later pruning pings are POST. plugins should simply respond with a 2xx status to a GET and sit idle, but after a POST they should respond with a 2xx and then call back to the Cryostat registration endpoint to refresh their access token
Ahh, thanks so much, your write up was very insightful into the back-end auth code and its motivation! I understand a lot better now. |
I've updated the PR with a few other minor fixes/changes.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than that, after going through everything the other day, everything looks good. Unless you still want to implement that feature of non-unique names of plugins that will be appended by some random id.
Good work!
I'll follow that up in a separate PR |
|
Fixes #1058
Fixes #1049
Fixes #1050
This PR implements the server side of discovery plugin JWT flow.
HTTP_API.md
contains a description of the flow as it relates to the endpoints.The general idea is this:
realm
name, which will become the root of a subtree in the overall discovery tree. This requires anAuthorization
header and standard authentication/authorization check, like most other API endpoints. If the request succeeds then Cryostat responds with a random UUID registration ID and a JWT access token. As with the other pre-existing JWT tokens, this one is signed and symmetrically encrypted, so it is completely opaque to the client or anyone else - only Cryostat can decrypt it.POST
to an endpoint with its registration UUID in the path and supply the access token as a query parameter. The access token includes information about the UUID andrealm
, as well as an expiry time, Cryostat's location (IP/host), and the location (IP) of the plugin. These factors are all verified to ensure that the client making the request is really the client for whom the token was originally generated. This ensures that no other API client, even ones that can pass a generalAuthorization
check, are allowed to publish discovery subtrees to the plugin'srealm
.DELETE
s again to an endpoint with the registration UUID in the path and the access token as a query parameter. The same token factor checks are performed.POST
registration flow with arealm
andcallback
, but additionally with the previoustoken
(which for this purpose, may be expired - but all other factors must match). In this case, rather than registering a new plugin, Cryostat simply generates a refreshed token and responds to the plugin in the same format. The client can now use this refreshed token for its update or deregistration requests.401
responses on update requests to determine that their token has expired, re-register to obtain a fresh one, and then re-attempt the update. Alternatively, Cryostat periodically pings plugins by their suppliedcallback
URLs. The ping period is half of the token expiry time, so it is guaranteed that a ping occurs before a token expires. The plugin may choose to pre-emptively re-register itself for a fresh token every time it receives such a ping so that its token is always fresh.