Conversation
754b0b3 to
b3557b7
Compare
|
@jon-wei thanks for raising a new PR, but it looks that this single patch contains quite large changes. Is it possible to split into several smaller PRs? It would be much helpful for reviewers. |
f077814 to
82f2e28
Compare
|
I can split the PR into two if really needed, but the changes are pretty self contained in an extension with few changes to core druid. Within the extension the authentication and authorization portions are also pretty self-contained. I'd rather keep this as one PR if possible since the features are closely related and to avoid the churn/overhead of having two PRs to go through the review process. |
|
I'm reviewing this now. |
|
@jon-wei ok. I'm also reviewing. |
bc64372 to
ac419d3
Compare
There was a problem hiding this comment.
@jon-wei thank you for the nice work! I did a first round of review. Also please let me know what do you think about the below.
- There is the sync issue between the coordinator and other nodes. So, an API to check the load status will be much helpful. See the lookup document as an example.
- I think credential data is usually not much big. What do you think about adding a coordinator API to sync over the whole cluster immediately?
There was a problem hiding this comment.
How does this relate to druid.auth.authenticator.MyBasicAuthenticator.initialInternalClientPassword?
There was a problem hiding this comment.
It should be set to the same value, unless the user changes the password later on
There was a problem hiding this comment.
What is the meaning of initial? Is it possible to change the password using other APIs?
There was a problem hiding this comment.
What happens if the password is changed and then the cluster is restarted? The adminPassword is reset to the initial one?
There was a problem hiding this comment.
initial is the password these users will have when the user is first automatically created.
The password can be changed afterwards and won't be reset after cluster restart, I added a comment about that.
There was a problem hiding this comment.
Should these configurations added to common.runtime.properties?
There was a problem hiding this comment.
Added a note about common.runtime.properties here
There was a problem hiding this comment.
Same with initialAdminPassword.
| public class DefaultBasicAuthorizerResourceHandler implements BasicAuthorizerResourceHandler | ||
| { | ||
| private static final Logger log = new Logger(DefaultBasicAuthorizerResourceHandler.class); | ||
| private static final Response NOT_FOUND_RESPONSE = Response.status(Response.Status.NOT_FOUND).build(); |
| this.resourceNamePattern = Pattern.compile(resourceAction.getResource().getName()); | ||
| } | ||
| catch (PatternSyntaxException pse) { | ||
| throw new BasicSecurityDBResourceException( |
|
|
||
| BasicAuthorizerPermission that = (BasicAuthorizerPermission) o; | ||
|
|
||
| if (getResourceAction() != null |
There was a problem hiding this comment.
You can use Objects.equals().
There was a problem hiding this comment.
This was autogenerated by IntelliJ
| public int hashCode() | ||
| { | ||
| int result = getResourceAction() != null ? getResourceAction().hashCode() : 0; | ||
| result = 31 * result + (getResourceNamePattern().pattern() != null |
There was a problem hiding this comment.
You can use Objects.hash().
There was a problem hiding this comment.
Also autogenerated, they're not used outside of tests
| connector = derbyConnectorRule.getConnector(); | ||
| tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get(); | ||
| connector.createConfigTable(); | ||
| //injector = setupInjector(); |
|
Thanks @jon-wei for working on the extension and @jihoonson for reviewing. Beyond what @jihoonson has said (especially an API to check the load status -- it's always helpful) I think we should cache the credential db on the local disk of each node, so when they start up, they can use their cache if the coordinator is not accessible. Maybe they should use their cache if they can't contact the coordinator within some amount of time. That way the coordinator being down doesn't torch the cluster completely. |
1301600 to
c937dc8
Compare
|
Thanks for the reviews, I addressed the comments and added the following features:
|
c937dc8 to
4f762cd
Compare
4f762cd to
1b1f022
Compare
| return objectMapper.writeValueAsBytes(userMap); | ||
| } | ||
| catch (IOException ioe) { | ||
| throw new ISE("WTF? Couldn't serialize userMap!"); |
There was a problem hiding this comment.
Please add ioe to the exception.
| return objectMapper.writeValueAsBytes(userMap); | ||
| } | ||
| catch (IOException ioe) { | ||
| throw new ISE("WTF? Couldn't serialize userMap!"); |
There was a problem hiding this comment.
Please add ioe to the exception.
| return objectMapper.writeValueAsBytes(roleMap); | ||
| } | ||
| catch (IOException ioe) { | ||
| throw new ISE("WTF? Couldn't serialize roleMap!"); |
There was a problem hiding this comment.
Please add ioe to the exception.
| private final EmittingLogger logger; | ||
| private final String threadName; | ||
|
|
||
| private Thread notifierThread; |
There was a problem hiding this comment.
Thread should be stopped on stop(). Suggest to use ExecutorService for easy maintenance.
There was a problem hiding this comment.
Added stop(), changed to use ExecutorService
| synchronized (itemsToUpdate) { | ||
| itemsToUpdate.add(updatedItemName); | ||
| serializedMaps.put(updatedItemName, updatedItemData); | ||
| itemsToUpdate.notify(); |
There was a problem hiding this comment.
It looks to the notifier notifies each update every time. Maybe it's better to collect updates for a given time (like 100 ms) and send them in bulk to reduce API calls. I'm not sure about this because I guess credentials are not frequently updated.
If you want to keep the current behavior, we don't have to keep itemsToUpdate and serializedMaps because there will be only a single element in them. Instead, I think it's better to use AtomicReference<Pair<String, byte[]>> or simply put a Pair<String, byte[]> to the blockingQueue if you decide to use it.
There was a problem hiding this comment.
I think the current behavior is okay for now, since the updates should be pretty infrequent, I changed this to use Pair<String, byte[]> and a BlockingQueue
| ) | ||
| { | ||
| this.storageUpdater = storageUpdater; | ||
| this.objectMapper = new ObjectMapper(new SmileFactory()); |
There was a problem hiding this comment.
Looks that objectMapper can be injected using @Smile annotation.
There was a problem hiding this comment.
Changed to use @Smile annotation
| } | ||
| } | ||
| catch (Exception e) { | ||
| LOG.makeAlert(e, "WTF? Could not deserialize user/role map received from coordinator.").emit(); |
There was a problem hiding this comment.
Is this intended to does nothing for all kinds of exceptions but emitting an alert?
There was a problem hiding this comment.
Yes, see the previous comment
| } | ||
|
|
||
| @Nullable | ||
| private UserAndRoleMap loadUserAndRoleMapFromDisk(String prefix) throws Exception |
There was a problem hiding this comment.
Can be reduced to throws IOException.
There was a problem hiding this comment.
Reduced to IOException
| ); | ||
| } | ||
|
|
||
| private void writeMapToDisk(String prefix, byte[] userMapBytes) throws Exception |
There was a problem hiding this comment.
Can be reduced to IOException.
There was a problem hiding this comment.
Reduced to IOException
| File cacheDir = new File(commonCacheConfig.getCacheDirectory()); | ||
| cacheDir.mkdirs(); | ||
| File userMapFile = new File(commonCacheConfig.getCacheDirectory(), getUserRoleMapFilename(prefix)); | ||
| Files.write(userMapBytes, userMapFile); |
There was a problem hiding this comment.
Similar comment. What if the write fails?
12e5a0e to
a8ae159
Compare
a8ae159 to
08a4e8f
Compare
| private final Map<String, BasicAuthDBConfig> itemConfigMap; | ||
| private final String baseUrl; | ||
| private final EmittingLogger logger; | ||
| private final String threadName; |
| HttpClient httpClient, | ||
| String baseUrl, | ||
| String threadName, | ||
| EmittingLogger logger |
There was a problem hiding this comment.
Ah it's needed. But I'm still concerned with that it's difficult to find where the logging code is from the log if the logging class is different from the actual class. What do you think about passing a caller name in constructor and adding it to all logs?
| return loadUserMapFromDisk(prefix); | ||
| } | ||
| catch (Exception e2) { | ||
| LOG.makeAlert(e2, "Encountered exception while loading user map snapshot for authenticator [%s]", prefix); |
There was a problem hiding this comment.
Please add e to e2 like e2.addSuppressed(e).
| return loadUserAndRoleMapFromDisk(prefix); | ||
| } | ||
| catch (Exception e2) { | ||
| LOG.makeAlert(e2, "Encountered exception while loading user-role map snapshot for authorizer [%s]", prefix); |
d81f604 to
895ce3e
Compare
|
@jon-wei thanks for the quick update! +1 after CI. |
|
@jihoonson thanks for the review! |
* Basic auth extension * Add auth configuration integration test * Fix missing authorizerName property * PR comments * Fix missing @JsonProperty annotation * PR comments * more PR comments
| { | ||
| private static final EmittingLogger LOG = new EmittingLogger(CommonCacheNotifier.class); | ||
|
|
||
| private static final List<String> NODE_TYPES = Arrays.asList( |
There was a problem hiding this comment.
@jon-wei does it purposely skip the Coordinator type?
There was a problem hiding this comment.
@leventov Yes, the coordinator gets its information about the auth state directly from metadata storage, this update notification is for other service types
|
Why basic security can't use EnvironmentVariablePasswordProvider, such as initialAdminPassword? |
|
@zhouyuanchao It can, check the master branch |
|
@jon-wei internalClientPassword can not use EnvironmentVariablePasswordProvider in master branch |
This PR adds an extension that provides implementations of the auth interfaces introduced in #4271 :
With the large amount of changes (almost a rewrite) from the last round of PR reviews on #4823, I felt like it would be easier to close the old PR and start a fresh one.
Some design notes: