-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Added an ability to authenticate requests to servers with jwtproxy #10252
Added an ability to authenticate requests to servers with jwtproxy #10252
Conversation
8dcb57e
to
2dc14d3
Compare
113344a
to
b9ef2ef
Compare
internalServers.put(key, value); | ||
} else { | ||
externalServers.put(key, value); | ||
// Server is external. Check if it should be secure or not | ||
if ("true".equals(value.getAttributes().get(ServerConfig.SECURE_SERVER_ATTRIBUTE))) { |
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.
Boolean.parseBoolean(value.getAttributes().get(ServerConfig.SECURE_SERVER_ATTRIBUTE)) ?
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.
Thanks for pointing. And it was implemented in this way for internal servers. I think it is done in this way because comparing with true
obviously shows that only this value is suitable here. Also, it is documented here that only true
will be considered as right value for internal/secure servers. While parseBoolean
checks value with equalsIgnoreCase
. I'd leave it as it is, at least for now.
b9ef2ef
to
a7ccfcb
Compare
7d5cf5d
to
9bd42b4
Compare
e7f237e
to
a7438c4
Compare
@garagatyi @mshaposhnik @skabashnyuk Now PR is ready to review, please take a look. |
4b53f31
to
ab14de5
Compare
ci-test |
ci-test build report: |
ab14de5
to
11a7d39
Compare
ci-test |
ci-test build report: |
Is this PR working or just a part of working new security system? |
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.
Overall looks good! There are inlined comments about the things I would love to understand more/address in the PR to provide a meaningful review.
|
||
@Inject | ||
public SecureServerExposerFactory( | ||
@Named("che.agents.auth_enabled") boolean agentsAuthEnabled, |
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 you elaborate why do we use agents
notion here instead of servers?
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.
Thank, Good point! It makes more sense to use servers
word there instead of agents
. I will change 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.
@garagatyi Do you think it may be che.server.secure_exposer
property which is Che System scope or it would be better to make it K8s/OS scope like che.infra.kubernetes.server_secure_exposer
?
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.
interesting question) On one hand it would be more complex to configure Che if we had several properties for several infras. On the other hand, we don't have anything like this for the Docker infra.
I think it is not so critical, and I would opt for the 1st option.
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, I will rename it to che.server.secure_exposer
and we'll rename it to infraspecific if it will be needed to have different authentication proxy backends for different infrastructures.
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 thought you are talking only about che.agents.secure_server_exposer
property.
As about che.agents.auth_enabled
it is not new but an existing one, it is used configuration CHE_AUTH_ENABLED
environment variable that is injected into each machine. I used it here as a flag if authentication is enabled or disabled, is Che launched in single user mode or multiuser. Are you OK to use it here, or do you think it makes sense to introduce new property?
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 OK to leave the old name. I missed that it is old.
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.
che.agents.auth_enabled
leaved as it is and che.agents.secure_server_exposer
is renamed to che.server.secure_exposer
} | ||
|
||
switch (serverExposer) { | ||
case "jwtproxy": |
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 this approach, this class has to know all the implementations. maybe it worth to rework the code in a way it is possible to bind other auth implementations?
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.
Makes sense, I'll rework it with Map bindings.
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.
Ouch. I missed that JwtProxySecureServerExposer
is created with factory that uses assisted inject. So, SecureServerExposerFactory could inject something like Map<String, SecureServerExposerImplFactory>
where SecureServerExposerImplFactory
is a custom interface
interface SecureServerExposerImplFactory {
SecureServerExpose create(RuntimeIdentity identity);
}
And then there should be corresponding SecureServerExposerImplFactory implementaion of each secure server (now JwtProxy and Default).
Looks like a bit overkill for now.
Do you think I should rework it or it is OK to leave it as it is for now?
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.
Maybe we can use a single interface of a factory of a factory instead of the map) This would allow using another implementation instead of JWT one (it can be even customized version of JWT one, depending on the ISV needs).
But yes, if there is no elegant and easy to implement and support way or if it will delay merging of the PR we can totally return to this issue when there would be a need for that.
And it looks like we definitely need to leave the approach as it is implemented now.
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.
Reworked in the suggested way.
import org.slf4j.LoggerFactory; | ||
|
||
/** @author Sergii Leshchenko */ | ||
public class SecureServerExposerFactory<T extends KubernetesEnvironment> { |
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.
Consider adding unit tests and javadocs
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
for (VerifierProxy verifierProxy : verifierProxies) { | ||
configBuilder.append( | ||
String.format( | ||
" - listen_addr: :%s\n" // :4471 |
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 that the usage of YAML serialization would improve readability and supportability of this code.
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've looked at this approach but it did not make code easier since I need to create 4 or even more additionally classes. I'll try again, maybe it would be better now =)
* | ||
* @author Sergii Leshchenko | ||
*/ | ||
public class JwtProxyConfigBuilder { |
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.
This doesn't seem related to k8s. Maybe it worth to move it to another module?
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, but I have no idea which module will be good to place for this class. I would leave it here while it is used by Kubernetes infrastructure. And move it to a new separated module or existing one when there will be a case where JwtProxyConfigBuilder will be used in another module, like Docker infrastructure. Is it OK 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.
Sure
import org.eclipse.che.workspace.infrastructure.kubernetes.server.ServerServiceBuilder; | ||
|
||
/** @author Sergii Leshchenko */ | ||
public class JwtProxyProvisioner { |
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.
Please, add javadocs
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
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer; | ||
|
||
/** | ||
* Expose secure servers with JWTProxy. |
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 you add more details into the javadocs? It would be helpful to understand what the class does and what for.
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
throws InfrastructureException { | ||
ensureJwtProxyInjected(k8sEnv); | ||
|
||
int listenPort = availablePort++; |
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.
Didn't get some use-cases. Would you be willing to explain them to me?
- What if 2 workspace masters are running at the same time?
- What if workspaces get deployed to the same single k8s namespace?
- Is there any guarantee that this class won't be created multiple times since it is not a singleton and is not created automatically by DI container?
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.
- What if 2 workspace masters are running at the same time?
There is no issue with that because of a restriction that only 1 Che Workspace Master is able to start a particular workspace at the same time.
- What if workspaces get deployed to the same single k8s namespace?
There are no any issues with that while it is a port of a jwtproxy pod for a particular workspace. Jwtproxy pod will have a unique name within k8s namespace that is set by UniqueNamesProvisioner
class.
- Is there any guarantee that this class won't be created multiple times since it is not a singleton and is not created automatically by DI container?
Actually, this class MUST BE created once for one STARTING
workspace, I don't see any possible ways in which we can guarantee that class is used correctly. What we can do - describe in java docs how it should be used.
One more thing that we could implement to prevent conflicts while incorrect usage of this class - use unique objects name - like machine name, pod name, etc. Then if this class will be created two or more times then the only thing that happens - there are more jwtproxy pods that are needed but not ports, objects conflicts should be there and everything would work as expected.
But I think it may be a bit overkill while this class is supposed to use in one place.
One more note, about unique names - do you think should we use a unique machine name instead of hardcoded? Now we always use jwtproxy
machine name. It would be better to use generated name with prefix like jwtproxy9sjXnw6y
to avoid conflicts when user have jwtproxy 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.
ok, I got it. Yeah, the current solution is good enough, overcomplicating it is not needed. Thanks for the clarification!
Actually, it requires a few more things to be working:
So, when these two steps will be merged, authentication secure servers with JWT proxy will be working in master branch (but disabled by default). |
Then I think we should not have release notes in this PR as it would not get into the product as something users can use. |
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.
Looks good!
It makes sense. I'll remove release notes section |
5a40729
to
b8e01f9
Compare
ci-test |
ci-test build report: |
@eclipse/eclipse-che-qa Could you please review test report? |
@sleshchenko In the functionality which is covered by selenium tests - new regression was not found. |
It allows infrastructure to save configuration into ConfigMap and mount it as files to Pods. JwtProxy config will be mounted to container in this way
b8e01f9
to
d6a913b
Compare
What does this PR do?
In this PR secure servers are introduced. If server supposed to be secure it should contain
secure: true
attribute, for example ws-agent server configuration:It is needed to make servers authentication independent.
Then infrastructure should cover server with authentication and server will receive only authenticated requests. Requests to secure servers must contain machine token in cookies,
Authorization
header ortoken
query parameter.This PR contains secure servers supporting of Kubernetes and OpenShift infrastructures.
For some time, it is not enabled by default, so secure servers still should authenticate requests themselves. It may be enabled by setting
che.agents.auth.secure_server_exposer=jwtproxy
.Kubernetes and OpenShift infrastructures implentation notes
It is done with JWTProxy, that is add in each Workspace as separated Pod. Original servers endpoints won't be accessible externally, instead JWTProxy port will be exposed and authenticated requests will be proxied to original server.
The same components works for Kubernetes and OpenShift infrasturcture since they reuse ExternalExposeServerStrategy (for K8s - ingress based, for OS - route based) for exposing JWTProxy service's ports.
What issues does this PR fix or reference?
#10081
Docs PR
I think Docs should be added when this feature won't be beta