-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Enable TLS on lens-k8s-proxy #4941
Conversation
Signed-off-by: Jari Kolehmainen <[email protected]>
export async function createKubeAuthProxyCertificateFiles(dir: string): Promise<string> { | ||
const cert = getKubeAuthProxyCertificate(); | ||
|
||
try { |
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.
Avoidable try/catch as part of business-logic, which is a code-smell. Try/catch is best reserved for fatal errors, and using it for something else damages credibility of eg. centralized error handling.
|
||
try { | ||
await access(dir); | ||
} catch { |
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.
"catch all" could be "catch specific" if decision is to use catch
here.
import path from "path"; | ||
import * as selfsigned from "selfsigned"; | ||
|
||
export function getKubeAuthProxyCertificate(): selfsigned.SelfSignedCert { |
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.
Does not require to be a function?
{ name: "organizationName", value: "Lens" }, | ||
]; | ||
|
||
return selfsigned.generate(opts, { |
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.
Is selfSigned.generate
deterministic? If not, it is an explicit side-effect, and should be a dependency instead.
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.
Not 100% deterministic because cert creation always creates an unique certificate. That said, it has deterministic fields... not sure where we should draw the line here.
}); | ||
} | ||
|
||
export function getKubeAuthProxyCertificatePath(baseDir: string) { |
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.
Thought: a convention is to use function
to advertise that this
has special significance. Here it does not, and lambda could be used to advertise that instead.
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 would disagree, this codebase only uses class
-es for this
significant code and lambdas can sometimes make the code harder to read.
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; | ||
import { createKubeAuthProxyCertificateFiles, getKubeAuthProxyCertificatePath } from "./kube-auth-proxy-cert"; | ||
|
||
const getKubeAuthProxyCertDirInjectable = getInjectable({ |
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.
Name of injectable
does not document unrelated stuff done in setup
.
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 would be better naming convention for injectables that have setup?
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; | ||
import { createKubeAuthProxyCertificateFiles, getKubeAuthProxyCertificatePath } from "./kube-auth-proxy-cert"; | ||
|
||
const getKubeAuthProxyCertDirInjectable = getInjectable({ |
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 could be wonderfully unit-testable either locally, or preferably as part of a larger system ;)
@@ -27,6 +29,7 @@ interface PrometheusServicePreferences { | |||
|
|||
interface Dependencies { | |||
createKubeAuthProxy: (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy; | |||
certPath: string; | |||
} | |||
|
|||
export class ContextHandler { |
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.
Note: some unrelated janky code around these parts needs a lovin' champion at some.
Signed-off-by: Jari Kolehmainen <[email protected]>
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
Conflicts have been resolved. A maintainer will review the pull request shortly. |
Signed-off-by: Jari Kolehmainen <[email protected]>
Signed-off-by: Jari Kolehmainen <[email protected]>
Signed-off-by: Jari Kolehmainen <[email protected]>
Signed-off-by: Jari Kolehmainen <[email protected]>
@@ -62,9 +62,11 @@ const di = getDi(); | |||
|
|||
app.setName(appName); | |||
|
|||
di.runSetups().then(() => { | |||
injectSystemCAs(); | |||
app.on("ready", async () => { |
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.
App start order was previously wrong... any async setups caused app.on("ready")
registration happen too late -> we missed it completely.
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 that we can catch these async errors I would suggest a slightly different shape:
async function main() {
const di = getDi();
await app.whenReady();
await di.runSetups();
...
}
main().catch(error => {
console.error(error);
dialog.showErrorBox("Lens Startup Failed", String(error));
app.exit(1);
});
Signed-off-by: Jari Kolehmainen <[email protected]>
Signed-off-by: Jari Kolehmainen <[email protected]>
import url from "url"; | ||
import { apiKubePrefix } from "../../common/vars"; | ||
import type { ProxyApiRequestArgs } from "./types"; | ||
|
||
const skipRawHeaders = new Set(["Host", "Authorization"]); | ||
|
||
export async function kubeApiRequest({ req, socket, head, cluster }: ProxyApiRequestArgs) { | ||
export async function kubeApiUpgradeRequest({ req, socket, head, cluster }: ProxyApiRequestArgs) { |
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.
Renamed because name was misleading, only upgrade requests are handled here.
Signed-off-by: Jari Kolehmainen <[email protected]>
async resolveAuthProxyCa() { | ||
return readFile(path.join(this.dependencies.certPath, "proxy.crt")); |
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 this file never changes I think it should be a setup-able dependency.
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.
Refactored. Now authProxyCa
is a dependency (but still a promise because of createContextHandlerInjectable
instantiate has to be non-async for now). This way ContextHandler
does not need to know readFile
at all.
@@ -62,9 +62,11 @@ const di = getDi(); | |||
|
|||
app.setName(appName); | |||
|
|||
di.runSetups().then(() => { | |||
injectSystemCAs(); | |||
app.on("ready", async () => { |
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 that we can catch these async errors I would suggest a slightly different shape:
async function main() {
const di = getDi();
await app.whenReady();
await di.runSetups();
...
}
main().catch(error => {
console.error(error);
dialog.showErrorBox("Lens Startup Failed", String(error));
app.exit(1);
});
|
||
instantiate: async (di) => { | ||
const userData = di.inject(directoryForUserDataInjectable); | ||
const certPath = getKubeAuthProxyCertificatePath(userData); |
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 sounds more like a dependency which should depend on directoryForUserDataInjectable
.
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.
Indeed, there was already an injectable for this. Refactored.
const userData = di.inject(directoryForUserDataInjectable); | ||
const certPath = getKubeAuthProxyCertificatePath(userData); | ||
|
||
return createKubeAuthProxyCertFiles(certPath, { |
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.
Given that this creates the files and then returns certPath
(which coincendentally should probably be called something like directoryForProxyCertificates
. I think that this injectable should actually be mostly just setup
and then instantiate should return undefined
or maybe a new SetupOnly
marker type.
Then whatever depends on createKubeAuthProxyCertFilesInjectable
could instead just depend on directoryForProxyCertificatesInjectable
and know that the setup and creation of the files will already have been done.
const binaryName = getBinaryName("lens-k8s-proxy"); | ||
const dependencies: KubeAuthProxyDependencies = { | ||
proxyBinPath: path.join(di.inject(directoryForBundledBinariesInjectable), binaryName), | ||
proxyCertPath: di.inject(getKubeAuthProxyCertDirInjectable), | ||
spawn, |
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 should be its own injectable, maybe within a folder src/main/child-process
since we probably shouldn't have any of those calls within renderer
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.
Done.
* Licensed under MIT License. See LICENSE in root directory for more information. | ||
*/ | ||
|
||
declare module "selfsigned" { |
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 it would be better if these were hosted into @types
. The PR process is actually really simple, I have done it a couple of times.
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 would be better yes but I think it can be separately from this PR. Note: this declaration is probably not complete, it only touches parts this PR needs.
Signed-off-by: Jari Kolehmainen <[email protected]>
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
Signed-off-by: Jari Kolehmainen <[email protected]>
Conflicts have been resolved. A maintainer will review the pull request shortly. |
Signed-off-by: Jari Kolehmainen <[email protected]>
@Nokel81 PTAL again. |
@@ -61,7 +61,7 @@ export class LensProxy extends Singleton { | |||
|
|||
public port: number; | |||
|
|||
constructor(protected router: Router, protected proxy: httpProxy, { shellApiRequest, kubeApiRequest, getClusterForRequest }: LensProxyFunctions) { | |||
constructor(protected router: Router, protected proxy: httpProxy, { shellApiRequest, kubeApiUpgradeRequest, getClusterForRequest }: LensProxyFunctions) { |
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: all of these feel like dependencies. Should we consolidate them?
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.
Probably yes but sounds like out of scope for this 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.
Sure
@@ -91,6 +92,8 @@ describe("<DeleteClusterDialog />", () => { | |||
beforeEach(async () => { | |||
const { mainDi, runSetups } = getDisForUnitTesting({ doGeneralOverrides: true }); | |||
|
|||
mainDi.override(createContextHandlerInjectable, () => () => undefined); |
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.
Should this be done in getDiForUnitTesting
so that it is always run by default?
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 cannot be there because some tests do require the real 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.
Oh yes, that is why I would put it behind some sort of flag (like doGeneralOverrides
is). I suggested moving it because I think that the vast majority of tests would want it overridden.
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 can change how certain injectables are overridden by default in a future PR
Enables secure TLS connections from clients (mainly
LensProxy
) tolens-k8s-proxy
. Follow-up PR will enable TLS also from clients (renderer etc.) toLensProxy
.