Skip to content
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

feat: validator: Load TLS configuration from ConfigMap #1119

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

akrejcir
Copy link
Collaborator

@akrejcir akrejcir commented Nov 1, 2024

What this PR does / why we need it:
This PR moves the TLS configuration of template-validator to a ConfigMap. It is mounded as a file in the pod, and validator is able to update its configuration without restarting the pod.

Which issue(s) this PR fixes:
Jira: https://issues.redhat.com/browse/CNV-28716

Release note:

None

These methods are not used from outside the package.

Signed-off-by: Andrej Krejcir <[email protected]>
Do not create or use the struct when using HTTP.

Signed-off-by: Andrej Krejcir <[email protected]>
This package was copied directly from vm-console-proxy:
https://github.com/kubevirt/vm-console-proxy/tree/main/pkg/filewatch

In a future commit, it will replace file watch logic in
internal/template-validator/tlsinfo.

We do this to simplify watching multiple directories with
TRS certificate and TLS configuration.

Signed-off-by: Andrej Krejcir <[email protected]>
Runs all callbacks before processing watch events. This means
that callbacks will have a change to notice the files after
the watch was started, but no events happened yet.

Signed-off-by: Andrej Krejcir <[email protected]>
@kubevirt-bot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please ask for approval from akrejcir. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubevirt-bot kubevirt-bot added the release-note-none Denotes a PR that doesn't merit a release note. label Nov 1, 2024
@kubevirt-bot kubevirt-bot added the dco-signoff: yes Indicates the PR's author has DCO signed all their commits. label Nov 1, 2024
@akrejcir
Copy link
Collaborator Author

akrejcir commented Nov 1, 2024

/cc @codingben @jcanocan @ksimon1

@codingben
Copy link
Member

@akrejcir Is it simialr to the implementation as in kubevirt/vm-console-proxy, e.g. filewatch.go? Maybe you can reuse this existing file to from kubevirt/vm-console-proxy to avoid code duplication?

@akrejcir
Copy link
Collaborator Author

akrejcir commented Nov 4, 2024

It is exactly the same package. I've mentioned it in the commit message: 3b2e200

Do you mean that SSP should import vm-console-proxy? I would rather avoid it, because it will cause unnecessary indirect dependencies.

internal/template-validator/tlsinfo/tlsinfo.go Outdated Show resolved Hide resolved
internal/template-validator/tlsinfo/tlsinfo.go Outdated Show resolved Hide resolved
internal/template-validator/validator/app.go Outdated Show resolved Hide resolved
internal/template-validator/tlsinfo/tlsinfo.go Outdated Show resolved Hide resolved
internal/template-validator/tlsinfo/tlsinfo.go Outdated Show resolved Hide resolved
internal/template-validator/tlsinfo/tlsinfo.go Outdated Show resolved Hide resolved
internal/template-validator/tlsinfo/tlsinfo.go Outdated Show resolved Hide resolved
})
It("should load certificate", func() {
Expect(tlsInfo.Init()).To(Succeed())
defer tlsInfo.Clean()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defer vs DeferCleanup?

Copy link
Collaborator Author

@akrejcir akrejcir Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defer is called when func() {} returns. DeferCleanup() is called after the It() test case, similarly to AfterEach(). It is useful to call in BeforeEach() to register cleanups that will be called after the test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make a difference using DeferCleanup here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think only a performance difference.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be moved to AfterEach()?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I prefer it this way. It is also how it's used in the validator code app.go.

internal/template-validator/tlsinfo/tlsinfo_test.go Outdated Show resolved Hide resolved
internal/template-validator/tlsinfo/tlsinfo_test.go Outdated Show resolved Hide resolved
internal/template-validator/tlsinfo/tlsinfo_test.go Outdated Show resolved Hide resolved
Using the filewatch package in template validator
will make it easier to watch multiple directories
in a future commit.

Signed-off-by: Andrej Krejcir <[email protected]>
The GetClientConfig() will be used in future commit to set
TLS options.

Signed-off-by: Andrej Krejcir <[email protected]>
The TLS configuration is read from a ConfigMap
that is mounted as a file. This allows updating
the configuration without restarting the pod.

Signed-off-by: Andrej Krejcir <[email protected]>
This will make it easier to modify the tests in future commit.

Signed-off-by: Andrej Krejcir <[email protected]>
Check TLS policy of template validator pod.

Signed-off-by: Andrej Krejcir <[email protected]>
Copy link

sonarcloud bot commented Nov 7, 2024

}

ti.minTLSVersion = minVersion
ti.cipherSuites = common.CipherIDs(tlsOptions.OpenSSLCipherNames, nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if this leaves us with an empty slice in case of user misconfiguration?

Copy link
Collaborator Author

@akrejcir akrejcir Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. If user passes unknown cipher names, they will be silently left out. That is how it is implemented in the library function crypto.OpenSSLToIANACipherSuites(). I will add code to return an error instead.

If ti.cipherSuites is empty slice, then no cipher suites are enabled, except TLS 1.3 ciphers, which cannot be disabled.

Copy link
Collaborator Author

@akrejcir akrejcir Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this, it may be desired behavior. Usually, on TCP connection, the server gives client a list of supported ciphers and the client chooses. Any unknown ciphers are ignored.
Maybe it is ok to use the same approach for configuration file. User could specify multiple ciphers, and it is not an error if the validator does not know some of them.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking what happens if you configure the API server with an empty slice if using TLS 1.1/1.2? Will it fallback to default ciphers? I'm not sure we should swallow this error silently. It is misconfiguration of the API server, not a failure in negotiating a cipher during a handshake.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we configure the server ciphers with empty slice [] and not use TLS 1.3, then no ciphers will be enabled, so it will not accept any connection. If we configure it with nil slice, then it will use default ciphers.
This is IMHO a bad API design in the standard library. Usually in other places there is no functional difference between [] and nil.

Ok, I will change the code, so it fails with error if ciphers in config file are not known.

Copy link
Collaborator Author

@akrejcir akrejcir Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented the error checking and noticed that OpenShift's TLS security profiles also contain ciphers that are not implemented in the TLS in golang and they are silently ignored.
For example, here is the TLS Intermediate profile:
https://github.com/openshift/api/blob/a2817b89f7e0989016967055a142d5e88bed18de/config/v1/types_tlssecurityprofile.go#L288-L303

TLSProfileIntermediateType: {
	Ciphers: []string{
		"TLS_AES_128_GCM_SHA256",
		"TLS_AES_256_GCM_SHA384",
		"TLS_CHACHA20_POLY1305_SHA256",
		"ECDHE-ECDSA-AES128-GCM-SHA256",
		"ECDHE-RSA-AES128-GCM-SHA256",
		"ECDHE-ECDSA-AES256-GCM-SHA384",
		"ECDHE-RSA-AES256-GCM-SHA384",
		"ECDHE-ECDSA-CHACHA20-POLY1305",
		"ECDHE-RSA-CHACHA20-POLY1305",
		"DHE-RSA-AES128-GCM-SHA256",
		"DHE-RSA-AES256-GCM-SHA384",
	},
	MinTLSVersion: VersionTLS12,
},

It contains ciphers DHE-RSA-AES128-GCM-SHA256 and DHE-RSA-AES256-GCM-SHA384, which are not available in the golang TLS library.
The code that does the translation between names and cipher IDs ignores them: https://github.com/openshift/library-go/blob/64780a4acea5ee3e565683ad96b1f4f0799f0bd2/pkg/crypto/crypto.go#L149-L180
https://github.com/openshift/library-go/blob/64780a4acea5ee3e565683ad96b1f4f0799f0bd2/pkg/crypto/crypto.go#L300-L314

So it is not trivial to check if a cipher name is valid. We don't want it to fail if users set one of the OpenShift's profiles, but they already contain unknown ciphers. So we would need to specifically allow them, even if they are not implemented.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as an empty slice that is not nil does not result in default values being used, I'm fine with it.

But strange that these profiles ship ciphers that are not supported. Are they supported in downstream distributions of golang?

})
It("should load certificate", func() {
Expect(tlsInfo.Init()).To(Succeed())
defer tlsInfo.Clean()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make a difference using DeferCleanup here?

tests/crypto_policy_test.go Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dco-signoff: yes Indicates the PR's author has DCO signed all their commits. release-note-none Denotes a PR that doesn't merit a release note. size/XL
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants