-
Notifications
You must be signed in to change notification settings - Fork 17
Conversation
@zregvart, thanks! @iocanel, @jimmidyson and @rhuss, please review this. |
private final Supplier<Long> timeSource; | ||
|
||
protected static final class RandomIvSource implements Supplier<byte[]> { | ||
private static final ThreadLocal<SecureRandom> RANDOM = new ThreadLocal<SecureRandom>() { |
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.
Isn't SecureRandom threadsafe? Do we really want to initialize (expensive) one for every thread we use this on?
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.
Yeah it is, this pattern is something I usually do, but now I remembered that we run in Linux containers so it will go to /dev/urandom
, it would block otherwise on Windows and that makes it needed. Good catch 👍
import com.fasterxml.jackson.databind.ObjectReader; | ||
import com.fasterxml.jackson.databind.ObjectWriter; | ||
|
||
import org.msgpack.jackson.dataformat.MessagePackFactory; |
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.
How much do we save using MessagePack vs JSON?
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.
i.e. is it worth introducing a new dependency for this?
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.
Looking at empty OAuth2CredentialFlowState (current working, ex CredentialFlowState) it's 30% difference (80 bytes JSON, 56 bytes MessagePack). We have 2.8KB of space in the Cookie to work with. If it's an issue easily 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.
I'm not fussed either way to be honest, msgpack format would bring in an extra 2 dependencies but that's no biggie. So I'll leave it up to you :)
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.
Yeah, let's use it when we see it's needed.
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.
BTW we have also think about productisation. As I've been told, every single dependency needs to be productised (i.e. needs to be build by red hat). See https://issues.jboss.org/browse/ENTESB-6963 for details and the dependency list we already have.
|
||
client: | ||
state: | ||
authenticationAlgorithm: HmacSHA1 |
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.
Did you mean to add these as default settings? How about leaving these out of here?
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.
I'm don't fully understand, you mean have them as default so that they don't need to be specified or remove them entirely?
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.
Sorry what I mean was, do we need this configuration in the application.yml that is bundled with the app at build time? Surely this is runtime configuration only and as such should be provided by the environment (via the syndesis-rest configmap in this case)? I don't like having stuff like encryption keys as defaults as it increases the chance of a publicly readable encryption key accidentally being used in a real deployment, e.g. if the configmap doesn't have the right configuration.
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.
So I mean remove them entirely.
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.
Yeah, that's what I was planing on doing, but in a separate PR as this needs some thinking behind it. Right now you can override these via environment variables. Not sure if we can have them dynamically set in the OpenShift templates.
Things to think about:
- do we need this statically or dynamically (think regenerate keys every n days)
- do we do this via k8s secrets
- if dynamically how to handle the transition (need n-1 keys)
- if dynamically then we need key distribution (spring cloud k8s, but it reloads the app context)
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.
I still would like these removed from the committed codebase due to the potential problems I mentioned above.
We could prevent startup if these values aren't set perhaps so we know they have to be set in prod deployments?
Keys should be regenerated, short lived keys are great, but you're right we need to think about how we do that. I'd recommend we do a rolling deployment rather than updating in place, but we also need to figure out how we accept old encrypted cookies and re-encrypt on next request if we do that. But this can be future PR work IMO.
I'd like to do more manual testing before I declare this done, but it should not change much so reviews are welcome 👍 |
Manually tested with Salesforce, works 👍 |
2e8740c
to
125f6c3
Compare
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.
Mostly looks really good, some refactoring changes that I think will help keep oauth1/2 flow logic more separate and easier to follow, but overall 👍. Thanks again @zregvart!
P.S. I know some of the requested changes are not changes necessarily made in this PR, but they relate to the same area of changes, i.e. making oauth1/2 flows more self contained and easier to follow. If it's going to significantly delay delivery or if the changes actually don't make sense, please let us know ASAP and we can look again in future.
} | ||
|
||
default String statePrefix() { | ||
if (this instanceof OAuth1CredentialFlowState) { |
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.
How about moving these down into the specific interfaces?
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.
yeah, silly me, good catch 👍
} | ||
|
||
default Type type() { | ||
if (this instanceof OAuth1CredentialFlowState) { |
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.
Same here: How about moving these down into the specific interfaces?
CredentialFlowState updateFrom(HttpServletRequest request); | ||
|
||
static Builder builderFor(final ConnectionFactory<?> connectionFactory) { | ||
if (connectionFactory instanceof OAuth1ConnectionFactory) { |
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.
And here... How about moving these down into the specific interfaces?
throw new IllegalStateException("Unsupported connection factory implementation: " + connectionFactory); | ||
} | ||
|
||
static Class<? extends CredentialFlowState> typeForName(final String name) { |
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.
Why is this necessary? Isn't jackson smart enough to do (de)serialize appropriately for subtypes?
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.
I think I need to give Jackson a concrete type, but I did fiddle with the annotations so it could be leftover from when id did not work for me. I think I need a unit test for that.
|
||
if (connectionFactory instanceof OAuth1ConnectionFactory) { | ||
redirectUrl = prepareOAuth1((OAuth1ConnectionFactory<?>) connectionFactory, flowStateBuilder, request); | ||
redirectUrl = prepareOAuth1((OAuth1ConnectionFactory<?>) connectionFactory, |
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.
Why not move all these prepareOAuth{1,2}
and similar methods into relevant connection factories?
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.
Good point, will try 👍
pom.xml
Outdated
@@ -543,6 +543,18 @@ | |||
<version>3.2.1</version> | |||
</dependency> | |||
|
|||
<dependency> | |||
<groupId>org.msgpack</groupId> |
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.
So you decided to keep these? That's cool, just misunderstood what you said before, but no worries.
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.
Oh, no, they're out, you know how I like to force push commits 😈
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.
Yeah, forgot to remove it from the parent
I think its well worth the effort to address the comments from the review in this PR, it should not be a lot of work. |
So with the latest commit there is a definite split between OAuth1 and OAuth2 implementations and this I think improved greatly the simplicity of the implementation. @jimmidyson could you have another look? |
I think the build is failing because deployment descriptor is missing environment variables trying to catch pod failing in system tests to confirm. |
Indeed:
|
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.
Minor nit, one question.
Much easier to follow - thanks again @zregvart!
|
||
} | ||
|
||
final class OAuthTokeConverter implements Converter<Map<String, String>, OAuthToken> { |
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.
nit: typo Toke
-> Token
/** | ||
* One configuration edition of settings used to store state on the client. | ||
*/ | ||
public abstract class Edition { |
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.
Bit confused by the name? Could you explain why it's called Edition
?
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.
Yeah I was trying to find a good name, the idea always was to periodically regenerate the keys, then each new generation would have tid=tid+1
, so as they roll in time I thought Edition would be a good name for it.
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.
OK LGTM. Please squash and let's wait until CI is passing.
...nt side state OAuth1 and OAuth2 implementations used different state storage, this changes that to use the same storage, which has been implemented on the client side as a cookie. For this the flow state had to be split into two variants one for OAuth1 and one for OAuth2, and state access had to be revised thought the implementation. This implements persisting state on the client side via secure cookies as defined in RFC6896[1]. This is basis for storing OAuth flow state in syndesisio#457. The configuration needed for this to function can be specified via environment variables: CLIENT_STATE_AUTHENTICATION_ALGORITHM _(e.g. HmacSHA1)_ CLIENT_STATE_AUTHENTICATION_KEY _(e.g. random 20 bytes)_ CLIENT_STATE_ENCRYPTION_ALGORITHM _(e.g. AES/CBC/PKCS5Padding)_ CLIENT_STATE_ENCRYPTION_KEY _(e.g. random 20 bytes)_ CLIENT_STATE_TID _(e.g. 1)_ If configuration is not defined random values are generated, wich will issue a warning as random values in pod instances will not failover or work after restart as the keys would differ. Also, separated OAuth1 and OAuth2 logic, it is now pushed in the `CredentialProvider`s so no more iffy logic in `Credentials`, yay! [1] https://tools.ietf.org/html/rfc6896 Fixes syndesisio#457
Pull request approved by @jimmidyson - applying approved label |
This is part of the work for #457, this can be viewed independently, so I'm creating a PR for early review/merge.