-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Add Support for Providing a custom ServiceAccountTokenStore through SecurityExtensions #126612
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
base: main
Are you sure you want to change the base?
Conversation
ec8d56c
to
98aa7e4
Compare
98aa7e4
to
3dec54b
Compare
@@ -5,7 +5,7 @@ | |||
* 2.0. |
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.
These had to move since they're now part of the SecurityExtension
interface through ServiceAccountTokenStore
.
@@ -53,22 +55,30 @@ public void stop() { | |||
} | |||
} | |||
|
|||
private ServiceAccountToken newMockServiceAccountToken(ServiceAccountId accountId, String tokenName, SecureString secret) { |
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.
Added this mocking instead of making the ServiceAccountToken
constructor public. The ServiceAccountToken
needs to be in core since it's part of the security extension interface. The ServiceAccountService
could also be moved to core, but that involves moving all service account definitions to core, which feels like too big of a refactor to justify.
); | ||
components.add(indexServiceAccountTokenStore); | ||
|
||
final FileServiceAccountTokenStore fileServiceAccountTokenStore = new FileServiceAccountTokenStore( |
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.
The FileServiceAccountTokenStore
is required by guice injection into TransportGetServiceAccountNodesCredentialsAction
so have to create it even if it's not used.
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.
It's techinically possible to not create TransportGetServiceAccountNodesCredentialsAction
(and other similar actions) as well if the extension store is found. It might be worth the effort since it is strange to have the API available when the underlying mechanism is effectively disabled.
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 agree with the line of reasoning, except I think it would be better for the action to exist and fail rather that to have a generic 404
in the Rest API.
Doing that right sounds like a multi-line / multi-file change, so perhaps we can revisit it in a future PR.
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.
TransportGetServiceAccountNodesCredentialsAction
used to rely on a FileServiceAccountTokenStore
implementation being provided as a component. I've now changed it to be a ReadOnlyServiceAccountTokenStore
. The security extension provides an instance of ReadOnlyServiceAccountTokenStore
that will fail any attempts to do local node token lookup, hence the API will fail. This allows us to still create TransportGetServiceAccountNodesCredentialsAction
but fail the action if called in serverless.
It's techinically possible to not create TransportGetServiceAccountNodesCredentialsAction (and other similar actions) as well if the extension store is found. It might be worth the effort since it is strange to have the API available when the underlying mechanism is effectively disabled.
I don't see any other places where transport actions are conditionally excluded in the security plugin, so that would be a new pattern I think? I think I prefer having the actions there, but the backing store preventing them from being used. Let me know if you prefer the 404 approach @ywangd .
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.
transport actions are conditionally excluded in the security plugin
We do exclude most actions if security is disabled? That said, I don't have a strong opinion on whether it should be 404. I also agree that we can tackle this separately.
@@ -149,14 +159,23 @@ public void createIndexToken( | |||
CreateServiceAccountTokenRequest request, | |||
ActionListener<CreateServiceAccountTokenResponse> listener | |||
) { | |||
if (indexServiceAccountTokenStore == null) { |
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.
We set indexServiceAccountTokenStore
to null
when an extension provides their own store. This is currently only used in serverless, so we'd never hit any of these. The impacted actions are:
GetServiceAccountNodesCredentialsAction
- Not needed since it's not used in serverless and the same creds are on all nodes.GetServiceAccountAction
- Not needed since it's not used in serverless.DeleteServiceAccountTokenAction
- Not needed since it's not used in serverless and this is immutable in serverless.CreateServiceAccountTokenAction
- Not needed since it's not used in serverless and this is immutable in serverless.ClearServiceAccountTokenStoreCacheAction
- Not needed since it's not used in serverless, we might want this in the future to clear the cache.
If a custom ServiceAccountTokenStore
is provided outside the serverless context it might be unexpected that some of these are not working. Any thoughts on that? My thinking is that we can only override the read-only
store and in doing so the read-write store (index) is disabled.
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.
If we skip creating the APIs as suggested earlier, we should be able to avoid some of these issues?
It seems possible and could be useful to support GetServiceAccountCredentials. But we can disable it for now and work on it as a follow-up.
If a custom ServiceAccountTokenStore is provided outside the serverless context it might be unexpected that some of these are not working.
I'd suggest we tighten the code to only allow internal implementation similar to this. There is no need to support other implementation.
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.
There is no need to support other implementation.
👍
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.
Since this is implemented as a SecurityExtension
we don't have visibility into which plugin this is (like in the linked example). I have changed the approach to conditionally create the IndexServiceAccountTokenStore
and FileServiceAccountTokenStore
and moved it all to a createServiceAccountService
method.
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 we can check the the number of provisions is 1 and the SecurityExtension
's class name to be internal similar to the linked example?
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, I didn't notice the internal check at first. I've added validation for that. Thanks!
Pinging @elastic/es-security (Team:Security) |
Hi @jfreden, I've created a changelog YAML for 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.
With the proposed changes, we assume no cluster-scoped service accounts. This is probably true and Tim and you may have already discussed and agreed on it. If that is the case, the changes look mostly good to me.
components.addAll(extensionTokenStores); | ||
serviceAccountService = new ServiceAccountService( | ||
client, | ||
new CompositeServiceAccountTokenStore(extensionTokenStores, client.threadPool().getThreadContext()) |
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'd prefer we ensure there is only a single extensionTokenStore and avoid the composite wrapping.
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.
That would be my inclination as well. We have no reason to support multiple stores, and we don't really know whether 2 arbitrary implementations would co-exist correctly.
); | ||
components.add(indexServiceAccountTokenStore); | ||
|
||
final FileServiceAccountTokenStore fileServiceAccountTokenStore = new FileServiceAccountTokenStore( |
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.
It's techinically possible to not create TransportGetServiceAccountNodesCredentialsAction
(and other similar actions) as well if the extension store is found. It might be worth the effort since it is strange to have the API available when the underlying mechanism is effectively disabled.
this.readOnlyServiceAccountTokenStore = indexServiceAccountTokenStore != null | ||
? new CompositeServiceAccountTokenStore( | ||
List.of(readOnlyServiceAccountTokenStore, indexServiceAccountTokenStore), | ||
client.threadPool().getThreadContext() | ||
) | ||
: readOnlyServiceAccountTokenStore; |
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'd rather pass in the readOnly store prepared and avoid creating it here by combining the index store.
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.
++
@@ -149,14 +159,23 @@ public void createIndexToken( | |||
CreateServiceAccountTokenRequest request, | |||
ActionListener<CreateServiceAccountTokenResponse> listener | |||
) { | |||
if (indexServiceAccountTokenStore == null) { |
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.
If we skip creating the APIs as suggested earlier, we should be able to avoid some of these issues?
It seems possible and could be useful to support GetServiceAccountCredentials. But we can disable it for now and work on it as a follow-up.
If a custom ServiceAccountTokenStore is provided outside the serverless context it might be unexpected that some of these are not working.
I'd suggest we tighten the code to only allow internal implementation similar to this. There is no need to support other implementation.
Yes, that is the intent. We do not have any environments where we want to support index-based service tokens and a customised token store. If we ever did have that, then an extension could supply an implementation of |
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 this looks pretty good. I haven't had a chance to review the tests, so I don't want to approve just yet, but I'm happy for you to merge if @ywangd approves it.
Otherwise, I'll come back to it in a day or 2.
); | ||
components.add(indexServiceAccountTokenStore); | ||
|
||
final FileServiceAccountTokenStore fileServiceAccountTokenStore = new FileServiceAccountTokenStore( |
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 agree with the line of reasoning, except I think it would be better for the action to exist and fail rather that to have a generic 404
in the Rest API.
Doing that right sounds like a multi-line / multi-file change, so perhaps we can revisit it in a future PR.
components.addAll(extensionTokenStores); | ||
serviceAccountService = new ServiceAccountService( | ||
client, | ||
new CompositeServiceAccountTokenStore(extensionTokenStores, client.threadPool().getThreadContext()) |
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.
That would be my inclination as well. We have no reason to support multiple stores, and we don't really know whether 2 arbitrary implementations would co-exist correctly.
this.readOnlyServiceAccountTokenStore = indexServiceAccountTokenStore != null | ||
? new CompositeServiceAccountTokenStore( | ||
List.of(readOnlyServiceAccountTokenStore, indexServiceAccountTokenStore), | ||
client.threadPool().getThreadContext() | ||
) | ||
: readOnlyServiceAccountTokenStore; |
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.
++
@@ -149,14 +159,23 @@ public void createIndexToken( | |||
CreateServiceAccountTokenRequest request, | |||
ActionListener<CreateServiceAccountTokenResponse> listener | |||
) { | |||
if (indexServiceAccountTokenStore == null) { |
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.
There is no need to support other implementation.
👍
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.
LGTM
The ReadOnlyServiceAccountTokenStore
interface looks a bit weird to me. I think being readable is by default and as such it should be part of the base ServiceAccountTokenStore
interface. The specialized interface should be mutable, i.e. index backed token store. Nevertheless, we can work on this separately.
// Completely handover service account token management to the extension if provided, | ||
// this will disable the index managed | ||
// service account tokens managed through the service account token API | ||
components.add(new PluginComponentBinding<>(ReadOnlyServiceAccountTokenStore.class, extensionStore.get())); |
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.
Related this comment #126612 (comment), I suggest we also checks the SecurityExtension's name to ensure it is an internal one, i.e. not support 3rd party implementation.
@@ -1250,6 +1247,56 @@ Collection<Object> createComponents( | |||
return components; | |||
} | |||
|
|||
private ServiceAccountService createServiceAccountService( |
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.
Can we add a test for the logic in this method?
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.
Added testing for this. Thanks!
I agree so I changed this to be a |
This PR adds support for providing a custom
ServiceAccountTokenStore
implementation in aSecurityExtension
. If at least oneSecurityExtension
provides a customServiceAccountTokenStore
the native and file based stores will be disabled and replaced by a read onlyCompositeServiceAccountTokenStore
of all extension provided token stores.