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

Configurable s3 endpoint for download #1163

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

Conversation

aaperis
Copy link
Contributor

@aaperis aaperis commented Dec 4, 2024

Related issue(s) and PR(s)
This PR closes #750.

Description
With this PR download can either be deployed as serving either unencrypted or encrypted files. The serveUnencryptedData boolean is removed as well as the logic for download to always generate an internal c4gh key-pair (that would allow the service to serve unencrypted files in case serveUnencryptedData=true).

Main changes:

  • download can now be configured to only serve unencrypted files if a filepath to a c4gh private key file is provided. Internally, the code checks the validity of the provided key by using the supplied passphrase to retrieve the corresponding c4gh public key. The service will not start if this procedure fails and the service will serve unencrypted data only if the retrieved public key is non-empty. This ensures that there can be no misconfiguration by e.g. providing malfunctioning keys etc. For example, the crypt4gh library and therefore reencrypt will use any string provided to it and encrypt a file but with this mechanism we avoid such a scary scenario.
  • if no filepath and passphrase are provided, then download will serve only encrypted files. This is the default behavior.

Other changes include:

  • go testsuite updates and some refactoring along the way
  • update of dev environment: in order to be able to test both setups at once, the docker compose files have been modified to include two instances of download listening at different ports, one serving encrypted and the other serving unencrypted files
  • update of integration tests, including a separate bash script for error messages returned to the user (according to the issue description)
  • update of helm chart

How to test
Integration tests pass.

@aaperis aaperis force-pushed the feature/unified-configurable-s3-endpoint-for-download branch from d5fc9f1 to d9c8803 Compare December 4, 2024 20:51
@aaperis aaperis changed the title Feature/unified configurable s3 endpoint for download Configurable s3 endpoint for download Dec 5, 2024
@aaperis aaperis force-pushed the feature/unified-configurable-s3-endpoint-for-download branch from d9c8803 to 73baacd Compare December 5, 2024 14:35
@aaperis aaperis self-assigned this Dec 9, 2024
@aaperis aaperis force-pushed the feature/unified-configurable-s3-endpoint-for-download branch 4 times, most recently from 9f68afa to d4e2439 Compare December 12, 2024 23:52
@aaperis aaperis marked this pull request as ready for review December 12, 2024 23:59
@aaperis aaperis requested a review from a team December 12, 2024 23:59
@aaperis aaperis force-pushed the feature/unified-configurable-s3-endpoint-for-download branch 2 times, most recently from 32a1128 to 058887a Compare December 13, 2024 08:10
charts/sda-svc/README.md Outdated Show resolved Hide resolved
charts/sda-svc/templates/download-deploy.yaml Outdated Show resolved Hide resolved
charts/sda-svc/templates/download-deploy.yaml Outdated Show resolved Hide resolved
charts/sda-svc/values.yaml Outdated Show resolved Hide resolved
charts/sda-svc/templates/download-deploy.yaml Outdated Show resolved Hide resolved
sda-download/internal/config/config_test.go Outdated Show resolved Hide resolved
sda-download/internal/config/config_test.go Outdated Show resolved Hide resolved
sda-download/internal/config/config_test.go Outdated Show resolved Hide resolved
sda-download/dev_utils/compose-no-tls.yml Outdated Show resolved Hide resolved
sda-download/dev_utils/config.yaml Show resolved Hide resolved
@aaperis aaperis force-pushed the feature/unified-configurable-s3-endpoint-for-download branch 2 times, most recently from 402b10a to 8a9b000 Compare December 15, 2024 11:47
@aaperis aaperis requested review from jbygdell and a team December 15, 2024 11:58
Copy link
Collaborator

@jbygdell jbygdell left a comment

Choose a reason for hiding this comment

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

Crypt4gh config already exists, there is not need to add another one since the app will only need one.

Either reuse the existing one or move everything up under the app sub heading.

sda-download/dev_utils/config.yaml Show resolved Hide resolved
@@ -194,7 +194,7 @@ echo "expected file found"
# Test file can be decrypted
## test also the files endpoint

C4GH_PASSPHRASE=$(grep -F passphrase config.yaml | sed -e 's/.* //' -e 's/"//g')
C4GH_PASSPHRASE=$(grep -F passphrase config.yaml | tail -1 | sed -e 's/.* //' -e 's/"//g')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
C4GH_PASSPHRASE=$(grep -F passphrase config.yaml | tail -1 | sed -e 's/.* //' -e 's/"//g')
C4GH_PASSPHRASE=$(grep -F passphrase config.yaml | sed -e 's/.* //' -e 's/"//g')

Shouldn't be needed since the config file only will have one cryptgh config block

Copy link
Collaborator

Choose a reason for hiding this comment

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

If you want to extract a specific entry using yq is the simplest solution.

yq '.app.c4gh.passphrase' config.yaml

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed all similar to using yq

sda-download/internal/config/config.go Show resolved Hide resolved
sda-download/internal/config/config.go Show resolved Hide resolved
sda-download/internal/config/config_test.go Show resolved Hide resolved
sda-download/internal/config/config_test.go Show resolved Hide resolved
sda-download/internal/config/config_test.go Show resolved Hide resolved
@aaperis
Copy link
Contributor Author

aaperis commented Dec 16, 2024

I unresolved the initial the conversations that I set as resolved (these are answered) so that @jbygdell can take a look and resolve. According to how we decided to do things on our last meeting about PRs.

dataset="https://doi.example/ty009.sfrrss/600.45asasga"
file="dummy_data"
clientkey=$(base64 -w0 client.pub.pem)
bad_token=token2=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZXF1ZXN0ZXJAZGVtby5vcmciLCJhdWQiOlsiYXVkMiIsImF1ZDMiXSwiYXpwIjoiYXpwIiwic2NvcGUiOiJvcGVuaWQiLCJpc3MiOiJodHRwczovL2RlbW8uZXhhbXBsZSIsImV4cCI6OTk5OTk5OTk5OSwiaWF0IjoxNTYxNjIxOTEzLCJqdGkiOiI2YWQ3YWE0Mi0zZTljLTQ4MzMtYmQxNi03NjVjYjgwYzIxMDIifQ.ncUyjNytxqS9bqLnsbjv6D839PnHVw-anQS4bFpAs20
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit confusing for providing a bad_token at the first glance.

Copy link
Contributor

@MalinAhlberg MalinAhlberg left a comment

Choose a reason for hiding this comment

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

Looks like good and thorough work..!
I left some questions, but otherwise I think it looks good!

sda-download/api/api.md Outdated Show resolved Hide resolved
sda-download/api/sda/sda.go Show resolved Hide resolved
@aaperis aaperis force-pushed the feature/unified-configurable-s3-endpoint-for-download branch from 516c1ae to 1dfc725 Compare December 19, 2024 16:17
@aaperis
Copy link
Contributor Author

aaperis commented Dec 19, 2024

I believe I have addressed all relevant comments and suggest we leave the renaming of variables for a refinement issue, especially since these are suggestions over changes made in the 1st round in response to 1st round renaming suggestions for the same variables.

@aaperis aaperis requested review from jbygdell, nanjiangshu, MalinAhlberg and a team December 19, 2024 16:27
Copy link
Contributor

@MalinAhlberg MalinAhlberg left a comment

Choose a reason for hiding this comment

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

I've tested this manually, and the s3 endpoint seems to work as expected ⭐ . However, the files endpoint is not supported when the service is in encrypted mode, the answer is always downloading unencrypted data is not supported. Maybe that's intended..? The docs says you can provide the header Public-Key, but I cannot get that to work. I also tried the Client-Public-Key header.
If this behavior is intended (or if someone can point out what I'm doing wrong), I will approve :)

@aaperis
Copy link
Contributor Author

aaperis commented Jan 7, 2025

I've tested this manually, and the s3 endpoint seems to work as expected ⭐ . However, the files endpoint is not supported when the service is in encrypted mode, the answer is always downloading unencrypted data is not supported. Maybe that's intended..? The docs says you can provide the header Public-Key, but I cannot get that to work. I also tried the Client-Public-Key header. If this behavior is intended (or if someone can point out what I'm doing wrong), I will approve :)

Good point @MalinAhlberg, just tried the same thing that you describe with the latest version from main and the behavior is the same. So the current behavior is at least consistent.

Not sure /files ever served encrypted data, from what I see in #695 and #761 all work for re-encryption is done on the s3 side only. For example see here. The c.Param("type") is only set to encryptedif as3` endpoint is called. Otherwise, I believe the default case in this switch statement seems to stream decrypted data, see here.

All in all, we need to discuss about this with the team.
Good catch with the docs!

Copy link
Contributor

@nanjiangshu nanjiangshu left a comment

Choose a reason for hiding this comment

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

Looks good!

Comment on lines +144 to +146
- name: APP_C4GH_PRIVATEKEYPATH
value: {{ template "c4ghPath" . }}/{{ .Values.global.download.serveDecrypted.c4ghKeyFile }}
- name: APP_C4GH_PASSPHRASE
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
- name: APP_C4GH_PRIVATEKEYPATH
value: {{ template "c4ghPath" . }}/{{ .Values.global.download.serveDecrypted.c4ghKeyFile }}
- name: APP_C4GH_PASSPHRASE
- name: C4GH_TRANSIENTPRIVATEKEYPATH
value: {{ template "c4ghPath" . }}/{{ .Values.global.download.serveDecrypted.c4ghKeyFile }}
- name: C4GH_TRANSIENTPASSPHRASE

Comment on lines +490 to +491
keyPath := viper.GetString("app.c4gh.privateKeyPath")
passphrase := viper.GetString("app.c4gh.passphrase")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
keyPath := viper.GetString("app.c4gh.privateKeyPath")
passphrase := viper.GetString("app.c4gh.passphrase")
keyPath := viper.GetString("c4gh.transientKeyPath")
passphrase := viper.GetString("c4gh.transientPassphrase")

Comment on lines +379 to +388
if viper.GetString("app.c4gh.privateKeyPath") != "" {

if !viper.IsSet("app.c4gh.passphrase") {
return errors.New("app.c4gh.passphrase is not set")
}

c.App.Crypt4GHPrivateKey, c.App.Crypt4GHPublicKeyB64, err = GetC4GHKeys()
if err != nil {
return err
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if viper.GetString("app.c4gh.privateKeyPath") != "" {
if !viper.IsSet("app.c4gh.passphrase") {
return errors.New("app.c4gh.passphrase is not set")
}
c.App.Crypt4GHPrivateKey, c.App.Crypt4GHPublicKeyB64, err = GetC4GHKeys()
if err != nil {
return err
}
if viper.GetString("c4gh.transientKeyPath") != "" {
if !viper.IsSet("c4gh.transientPassphrase") {
return errors.New("c4gh.transientPassphrase is not set")
}
c.C4GH.PrivateKey, c.C4GH.PublicKeyB64, err = GetC4GHKeys()
if err != nil {
return err
}

If this is done something like this, we wont end up with to much of a conflict when we merge download int the rest of sda. transient might not be the best prefix but as long as it is clear it should be fine.

tl;dr;
Create an entry in the Map struct for the C4GH keypair and remove it from the AppConfig entry above. Since the AppConfig holds the server config, something that will be shared by all our APIs in the sda folder (curently there are some duplicaitons that needs to be removed there.)

Comment on lines +99 to +100
viper.Set("app.c4gh.PrivateKeyPath", privateKeyFile.Name())
viper.Set("app.c4gh.passphrase", "password")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
viper.Set("app.c4gh.PrivateKeyPath", privateKeyFile.Name())
viper.Set("app.c4gh.passphrase", "password")
viper.Set("c4gh.transientKeyPath", privateKeyFile.Name())
viper.Set("c4gh.transientPassphrase", "password")

publicKey, err := base64.StdEncoding.DecodeString(c.App.Crypt4GHPublicKeyB64)
assert.Nilf(suite.T(), err, "Incorrect public c4gh key generated (error in base64 encoding)")
_, err = keys.ReadPublicKey(bytes.NewReader(publicKey))
assert.Nilf(suite.T(), err, "Incorrect public c4gh key generated (bad key)")

// Check false c4gh key
viper.Set("app.c4gh.privateKeyPath", "some/nonexistent.key")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
viper.Set("app.c4gh.privateKeyPath", "some/nonexistent.key")
viper.Set("c4gh.transientKeyPath", "some/nonexistent.key")

Comment on lines +413 to +415
viper.Set("app.c4gh.privateKeyPath", privateKeyFilePath)
viper.Set("app.c4gh.passphrase", "password")
config.Config.App.Crypt4GHPrivateKey, config.Config.App.Crypt4GHPublicKeyB64, err = config.GetC4GHKeys()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
viper.Set("app.c4gh.privateKeyPath", privateKeyFilePath)
viper.Set("app.c4gh.passphrase", "password")
config.Config.App.Crypt4GHPrivateKey, config.Config.App.Crypt4GHPublicKeyB64, err = config.GetC4GHKeys()
viper.Set("c4gh.transientKeyPath", privateKeyFilePath)
viper.Set("c4gh.transientPassphrase", "password")
config.Config.C4GH.PrivateKey, config.Config.C4GH.PublicKeyB64, err = config.GetC4GHKeys()

Comment on lines +405 to +406
originalServeUnencryptedDataTrigger := config.Config.App.Crypt4GHPublicKeyB64
originalC4ghPrivateKeyFilepath := config.Config.App.Crypt4GHPrivateKey
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
originalServeUnencryptedDataTrigger := config.Config.App.Crypt4GHPublicKeyB64
originalC4ghPrivateKeyFilepath := config.Config.App.Crypt4GHPrivateKey
originalServeUnencryptedDataTrigger := config.Config.C4GH.PublicKeyB64
originalC4ghPrivateKeyFilepath := config.Config.C4GH.PrivateKey

Comment on lines +344 to +345
originalServeUnencryptedDataTrigger := config.Config.App.Crypt4GHPublicKeyB64
config.Config.App.Crypt4GHPublicKeyB64 = ""
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
originalServeUnencryptedDataTrigger := config.Config.App.Crypt4GHPublicKeyB64
config.Config.App.Crypt4GHPublicKeyB64 = ""
originalServeUnencryptedDataTrigger := config.Config.C4GH.PublicKeyB64
config.Config.C4GH.PublicKeyB64 = ""

@@ -329,7 +330,7 @@ func Download(c *gin.Context) {
ListBuckets(c)

case c.Param("filename") != "":
if strings.HasPrefix(c.Request.URL.Path, "/s3-encrypted") {
if config.Config.App.Crypt4GHPublicKeyB64 == "" {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if config.Config.App.Crypt4GHPublicKeyB64 == "" {
if config.Config.C4GH.PublicKeyB64 == "" {

// when Crypt4GHPublicKeyB64 is not set or empty, but this is not the case for calls to /files endpoint.
// So we need this check.

if c.Param("type") != "encrypted" && config.Config.App.Crypt4GHPublicKeyB64 == "" {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if c.Param("type") != "encrypted" && config.Config.App.Crypt4GHPublicKeyB64 == "" {
if c.Param("type") != "encrypted" && config.Config.CGH.PublicKeyB64 == "" {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[download] Configurable option for whether to support decrypted xor encrypted downloads.
4 participants