-
-
Notifications
You must be signed in to change notification settings - Fork 544
feat(cassandra): add ssl option cassandra #3151
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?
feat(cassandra): add ssl option cassandra #3151
Conversation
✅ Deploy Preview for testcontainers-go ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
# Conflicts: # go.sum
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.
Looking good, although I added some comments about using the same approach as in the Redis module. Could you please take a look?
Cheers!
modules/cassandra/cassandra.go
Outdated
), | ||
} | ||
|
||
c := &CassandraContainer{} |
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.
suggestion: keep this initialisation next to its usage. Seems moved here for no reasons
modules/cassandra/cassandra.go
Outdated
}, | ||
WaitingFor: wait.ForAll( | ||
wait.ForListeningPort(port), | ||
wait.ForExec([]string{"cqlsh", "-e", "SELECT bootstrapped FROM system.local"}).WithResponseMatcher(func(body io.Reader) bool { | ||
data, _ := io.ReadAll(body) | ||
return strings.Contains(string(data), "COMPLETED") | ||
}), | ||
}).WithStartupTimeout(2*time.Minute), |
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.
question: is 2 minutes too long? Can we use a shorter duration?
modules/cassandra/cassandra.go
Outdated
"CASSANDRA_SKIP_WAIT_FOR_GOSSIP": "1", | ||
"CASSANDRA_START_NATIVE_TRANSPORT": "true", |
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.
question: are these new env vars related to the SSL options? If not, I'd separate the changes into a new PR, if possible
modules/cassandra/cassandra.go
Outdated
} | ||
|
||
// WithTLS is an alias for WithSSL for user convenience | ||
func WithTLS(opts SSLOptions) testcontainers.CustomizeRequestOption { |
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.
question: why adding two flavours of the same option? I'd just choose one to avoid creating confusion on users
modules/cassandra/cassandra.go
Outdated
} | ||
|
||
// Mark that SSL is enabled for later use | ||
req.Labels = mergeMap(req.Labels, map[string]string{"testcontainers.cassandra.ssl": "true"}) |
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.
question: I guess this is needed to notify the Run function about the SSL addition, right? I'd choose a different pattern for this, creating an options
type as in the redis module. Then, you can simply generate the SSL certs on-the-fly as in that module.
I made changes, can you please check again? |
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 the quick updates, here's some suggestion which focus on simplification and reducing API surface area.
modules/cassandra/options.go
Outdated
IsTLSEnabled bool | ||
TLSConfig *tls.Config |
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.
suggestion: make these private so there's less API surface area allowing for refactoring without causing breaking changes. Given it's typed nature we can skip the Is prefix for boolean flag too.
IsTLSEnabled bool | |
TLSConfig *tls.Config | |
tlsEnabled bool | |
tlsConfig *tls.Config |
More fixes to align with this change obviously, so best to just rename in your editor.
// GenerateJKSKeystore generates a JKS keystore with a self-signed cert using keytool, and extracts the public cert for Go client trust. | ||
func GenerateJKSKeystore() (keystorePath, certPath string, err error) { |
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.
suggestion: make private, again less API surface area to maintain. Also needs to use go not an external tool which might not be present on all platforms. See other modules on how they generate keys, hopefully that's compatible with cassandra.
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.
@stevenh making this function private, i need to change in options_test.go, its ok?
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.
Either that or make a seperate test file.
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.
options_test.go is created by me only but i cannot access that function as testcases are depend on it, do you want me to make this function private and remove options_test.go?
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.
Up to you, having integration and unit tests are both good. In go its not a problem to use the main package for tests, as long as you're conscious about the seperation.
However can we should first see if we can eliminate this external dependency. You can see a similar example here: https://github.com/testcontainers/testcontainers-go/blob/main/modules/redis/options.go#L86 which uses https://pkg.go.dev/github.com/mdelapenya/tlscert
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 seems you're suggesting we either minimize SSL-related dependencies or simply support SSL with minimal setup. I always try to follow the existing repository codebase and coding style as much possible.
Initially, I tried using only tlscert, but it has some limitations. If we choose to use tlscert, we can eliminate the keytool dependency, but we'd then need to rely on openssl.
So, we have two options:
Use keytool as we currently do.
Follow an approach similar to Cassandra's Go driver, where certificates are added in a testdata folder and used for SSL testing, as seen in the official Go driver.
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.
tlscert would be the preferred option as it's what a lot of the other modules use.
If it needs changes that should be quick and easy as that module is written by @mdelapenya one of this repositories maintainers.
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 know but it can handle this cassandra cert generation? @mdelapenya ? i already tried that let me know if ok to use openssl then i will make change to tlscert.
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.
Yes, let's use it for consistency. If there is a missing feature in that library, let's open an issue in there and work on fix and release it
Cheers!
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.
A Quick Look and it seems like it can use standard pen formatted certs so it should just work
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.
A few bugs introduced here I think, the big question is eliminating the use of external dependency keytool
// GenerateJKSKeystore generates a JKS keystore with a self-signed cert using keytool, and extracts the public cert for Go client trust. | ||
func GenerateJKSKeystore() (keystorePath, certPath string, err error) { |
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.
Up to you, having integration and unit tests are both good. In go its not a problem to use the main package for tests, as long as you're conscious about the seperation.
However can we should first see if we can eliminate this external dependency. You can see a similar example here: https://github.com/testcontainers/testcontainers-go/blob/main/modules/redis/options.go#L86 which uses https://pkg.go.dev/github.com/mdelapenya/tlscert
@@ -14,28 +16,35 @@ import ( | |||
) | |||
|
|||
const ( | |||
port = nat.Port("9042/tcp") | |||
port = nat.Port("9042/tcp") | |||
securePort = nat.Port("9142/tcp") // Common port for SSL/TLS connections |
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.
question: do we need to two ports or can it just be one which is either SSL or not?
waitStrategies := []wait.Strategy{ | ||
wait.ForListeningPort(port), | ||
wait.ForExec([]string{"cqlsh", "-e", "SELECT bootstrapped FROM system.local"}).WithResponseMatcher(func(body io.Reader) bool { | ||
data, _ := io.ReadAll(body) | ||
return strings.Contains(string(data), "COMPLETED") | ||
}).WithStartupTimeout(1 * time.Minute), | ||
} |
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.
@mdelapenya looks like we have an issue with WithWaitStrategy, it sets vs appending to WaitFor preventing easy initialisation here.
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. I think the use case here would be appending. I can submit a fix now
|
||
// Apply wait strategy using the correct method | ||
if err := testcontainers.WithWaitStrategy(wait.ForAll(waitStrategies...)).Customize(&genericContainerReq); err != nil { | ||
return nil, err |
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.
suggestion: wrap the error here
return nil, err | |
return nil, fmt.Errorf("with wait strategy: %w", err) |
// TLSConfig returns the TLS configuration for the Cassandra container, nil if TLS is not enabled. | ||
func (c *CassandraContainer) TLSConfig() *tls.Config { |
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.
@mdelapenya quite a common pattern in modules, we should consider returning an error 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.
You mean the pointer to the config and the error, right? I think we changed the tlscert library precisely for that, so good to me to 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.
Nope, these container methods, have them return an error if the config is nil vs just returning nil as the caller should know if they enabled TLS or not.
func (c *CassandraContainer) TLSConfig() (*tls.Config, error) {
if c.settings.tlsConfig == nil {
return nil, errors.New("tls not enabled")
}
return c.settings.tlsConfig.Config, nil
}
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 I see, the recently added methods for Redis and Valkey. For Valkey, we are in the same release cycle, we can change it without BC, for Redis, it's a BC
func (o Option) Customize(req *testcontainers.GenericContainerRequest) error { | ||
return o(req, &Options{}) |
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.
bug: while this will run there options won't be accessible, so should still be just a noop.
We're looking to refactor how this works in a future version to avoid this, but that will be a bit.
} | ||
|
||
genericContainerReq := testcontainers.GenericContainerRequest{ | ||
ContainerRequest: req, | ||
Started: true, | ||
} | ||
|
||
var settings Options |
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.
bug: settings is never set, you still need to check if you have an Option in the for loop and process it to ensure it set correctly.
If your tests are passing they shouldn't so might need a fix there too.
// TLSConfig represents the TLS configuration for Cassandra | ||
type TLSConfig struct { |
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.
question: are the paths needed? If not we can just return *tls.Config
. I suspect the answer is dependent on eliminating the need for keytool
#3124
What does this PR do?
Why is it important?
Related issues
@mdelapenya Can you please review