diff --git a/Dockerfile b/Dockerfile index 4fd90f0..76d1492 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ENV CGO_ENABLED=0 COPY . . -RUN go build -buildvcs=false -o ./sda-download ./cmd +RUN set -ex; for p in cmd/*; do test -d "$p" && go build -buildvcs=false -o "sda-${p#cmd/}" "./$p"; done RUN echo "nobody:x:65534:65534:nobody:/:/sbin/nologin" > passwd FROM scratch diff --git a/cmd/client/client.go b/cmd/client/client.go new file mode 100644 index 0000000..8549055 --- /dev/null +++ b/cmd/client/client.go @@ -0,0 +1,38 @@ +package main + +import ( + "context" + "flag" + "log" + "time" + + re "github.com/neicnordic/sda-download/internal/reencrypt" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +var ( + addr = flag.String("addr", "localhost:5051", "the address to connect to") + publickey = flag.String("publickey", "NZfoJzFcOli3UWi/7U624h6fv2PufL1i2QPK8JkpmFg=", "Name to greet") + fileid = flag.String("fileid", "urn:neic:001-002", "Name to greet") +) + +func main() { + flag.Parse() + // Set up a connection to the server. + conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer conn.Close() + c := re.NewReencryptClient(conn) + + // Contact the server and print out its response. + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := c.ReencryptHeader(ctx, &re.ReencryptRequest{Fileid: *fileid, Publickey: *publickey}) + if err != nil { + log.Fatalf("could not greet: %v", err) + } + log.Printf("Greeting: %s", string(r.GetHeader())) +} diff --git a/cmd/main.go b/cmd/download/download.go similarity index 98% rename from cmd/main.go rename to cmd/download/download.go index 46c21aa..fdf3e30 100644 --- a/cmd/main.go +++ b/cmd/download/download.go @@ -18,7 +18,7 @@ func init() { log.Info("(1/5) Loading configuration") // Load configuration - conf, err := config.NewConfig() + conf, err := config.NewConfig("download") if err != nil { log.Panicf("configuration loading failed, reason: %v", err) } diff --git a/cmd/reencrypt/reencrypt.go b/cmd/reencrypt/reencrypt.go new file mode 100644 index 0000000..6d6813b --- /dev/null +++ b/cmd/reencrypt/reencrypt.go @@ -0,0 +1,88 @@ +package main + +import ( + "context" + "fmt" + "net" + "strings" + + log "github.com/sirupsen/logrus" + + "github.com/neicnordic/crypt4gh/keys" + "github.com/neicnordic/crypt4gh/model/headers" + "golang.org/x/crypto/chacha20poly1305" + + "github.com/neicnordic/sda-download/internal/config" + "github.com/neicnordic/sda-download/internal/database" + re "github.com/neicnordic/sda-download/internal/reencrypt" + "google.golang.org/grpc" +) + +// server is used to implement reencrypt.ReEncryptServer. +type server struct { + re.UnimplementedReencryptServer +} + +// init is run before main, it sets up configuration and other required things +func init() { + log.Info("(1/5) Loading configuration") + + // Load configuration + conf, err := config.NewConfig("reencrypt") + if err != nil { + log.Panicf("configuration loading failed, reason: %v", err) + } + config.Config = *conf + + // Connect to database + db, err := database.NewDB(conf.DB) + if err != nil { + log.Panicf("database connection failed, reason: %v", err) + } + defer db.Close() + database.DB = db + +} + +// Reencrypt implements reencrypt.ReEncryptServer +func (s *server) ReencryptHeader(ctx context.Context, in *re.ReencryptRequest) (*re.ReencryptResponse, error) { + log.Debugf("Received Public key: %v", in.GetPublickey()) + log.Debugf("Received fileid: %v", in.GetFileid()) + // Get file header + fileDetails, err := database.GetFile(in.GetFileid()) + if err != nil { + + return nil, err + } + + newReaderPublicKey, err := keys.ReadPublicKey(strings.NewReader("-----BEGIN CRYPT4GH PUBLIC KEY-----\n" + in.GetPublickey() + "\n-----END CRYPT4GH PUBLIC KEY-----\n")) + if err != nil { + return nil, err + } + + newReaderPublicKeyList := [][chacha20poly1305.KeySize]byte{} + newReaderPublicKeyList = append(newReaderPublicKeyList, newReaderPublicKey) + + log.Debugf("header: %v", fileDetails.Header) + log.Debugf("crypt4ghkey path: %v", *config.Config.Grpc.Crypt4GHKey) + + newheader, err := headers.ReEncryptHeader(fileDetails.Header, *config.Config.Grpc.Crypt4GHKey, newReaderPublicKeyList) + if err != nil { + return nil, err + } + + return &re.ReencryptResponse{Header: newheader}, nil +} + +func main() { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *&config.Config.Grpc.Port)) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + s := grpc.NewServer() + re.RegisterReencryptServer(s, &server{}) + log.Printf("server listening at %v", lis.Addr()) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} diff --git a/dev_utils/compose-no-tls.yml b/dev_utils/compose-no-tls.yml index c66c58b..dcb1ac9 100644 --- a/dev_utils/compose-no-tls.yml +++ b/dev_utils/compose-no-tls.yml @@ -1,5 +1,4 @@ services: - db: command: server /data container_name: db @@ -19,48 +18,68 @@ services: volumes: - /tmp/data:/data - s3: - command: server /data - container_name: s3 - environment: - - MINIO_ACCESS_KEY=access - - MINIO_SECRET_KEY=secretkey - healthcheck: - test: ["CMD", "curl", "-fq", "http://localhost:9000/minio/health/live"] - interval: 5s - timeout: 20s - retries: 3 - image: minio/minio:RELEASE.2021-11-24T23-19-33Z - ports: - - "9000:9000" + # s3: + # command: server /data + # container_name: s3 + # environment: + # - MINIO_ACCESS_KEY=access + # - MINIO_SECRET_KEY=secretkey + # healthcheck: + # test: ["CMD", "curl", "-fq", "http://localhost:9000/minio/health/live"] + # interval: 5s + # timeout: 20s + # retries: 3 + # image: minio/minio:RELEASE.2021-11-24T23-19-33Z + # ports: + # - "9000:9000" - createbucket: - container_name: buckets - image: minio/mc - depends_on: - s3: - condition: service_healthy - entrypoint: > - /bin/sh -c " - /usr/bin/mc config host add s3 http://s3:9000 access secretkey; - /usr/bin/mc mb s3/archive; - exit 0; - " - restart: on-failure + # createbucket: + # container_name: buckets + # image: minio/mc + # depends_on: + # s3: + # condition: service_healthy + # entrypoint: > + # /bin/sh -c " + # /usr/bin/mc config host add s3 http://s3:9000 access secretkey; + # /usr/bin/mc mb s3/archive; + # exit 0; + # " + # restart: on-failure + + # download: + # command: sda-download + # container_name: download + # depends_on: + # db: + # condition: service_healthy + # s3: + # condition: service_healthy + # mockauth: + # condition: service_started + # environment: + # - ARCHIVE_URL=http://s3 + # - ARCHIVE_TYPE=s3 + # - DB_HOST=db + # image: neicnordic/sda-download:latest + # build: + # context: .. + # volumes: + # - ./config-notls.yaml:/config.yaml + # - ./:/dev_utils/ + # - ./archive_data/4293c9a7-dc50-46db-b79a-27ddc0dad1c6:/tmp/4293c9a7-dc50-46db-b79a-27ddc0dad1c6 + # mem_limit: 256m + # ports: + # - "8080:8080" + # restart: always - download: - command: sda-download - container_name: download + reencrypt: + command: sda-reencrypt + container_name: reencrypt depends_on: db: condition: service_healthy - s3: - condition: service_healthy - mockauth: - condition: service_started environment: - - ARCHIVE_URL=http://s3 - - ARCHIVE_TYPE=s3 - DB_HOST=db image: neicnordic/sda-download:latest build: @@ -68,28 +87,26 @@ services: volumes: - ./config-notls.yaml:/config.yaml - ./:/dev_utils/ - - ./archive_data/4293c9a7-dc50-46db-b79a-27ddc0dad1c6:/tmp/4293c9a7-dc50-46db-b79a-27ddc0dad1c6 - mem_limit: 256m - ports: - - "8080:8080" - restart: always - - mockauth: - command: - - /bin/sh - - -c - - | - pip install --upgrade pip - pip install aiohttp Authlib - python -u /mockoidc.py - container_name: mockauth - image: python:3.10-slim - volumes: - - ./mockoidc/mockoidc.py:/mockoidc.py mem_limit: 256m ports: - - "8000:8000" + - "5051:5051" restart: always -volumes: - archive: + # mockauth: + # command: + # - /bin/sh + # - -c + # - | + # pip install --upgrade pip + # pip install aiohttp Authlib + # python -u /mockoidc.py + # container_name: mockauth + # image: python:3.10-slim + # volumes: + # - ./mockoidc/mockoidc.py:/mockoidc.py + # mem_limit: 256m + # ports: + # - "8000:8000" + # restart: always +# volumes: +# archive: diff --git a/dev_utils/run_integration_test_no_tls.sh b/dev_utils/run_integration_test_no_tls.sh index 0e19b6d..5ac3068 100644 --- a/dev_utils/run_integration_test_no_tls.sh +++ b/dev_utils/run_integration_test_no_tls.sh @@ -1,31 +1,31 @@ #!/bin/sh -for c in s3cmd jq -do - if ! command -v $c - then - echo "$c could not be found" - exit 1 - fi -done - -cat << EOF > c4gh.pub.pem ------BEGIN CRYPT4GH PUBLIC KEY----- -avFAerx0ZWuJE6fTI8S/0wv3yMo1n3SuNTV6zvKdxQc= ------END CRYPT4GH PUBLIC KEY----- -EOF - -chmod 444 c4gh.pub.pem - -cat << EOF > c4gh.sec.pem ------BEGIN CRYPT4GH ENCRYPTED PRIVATE KEY----- -YzRnaC12MQAGc2NyeXB0ABQAAAAAwAs5mVkXda50vqeYv6tbkQARY2hhY2hhMjBf -cG9seTEzMDUAPAd46aTuoVWAe+fMGl3VocCKCCWmgFUsFIHejJoWxNwy62c1L/Vc -R9haQsAPfJMLJSvUXStJ04cyZnDHSw== ------END CRYPT4GH ENCRYPTED PRIVATE KEY----- -EOF - -chmod 444 c4gh.sec.pem +# for c in s3cmd jq +# do +# if ! command -v $c +# then +# echo "$c could not be found" +# exit 1 +# fi +# done + +# cat << EOF > c4gh.pub.pem +# -----BEGIN CRYPT4GH PUBLIC KEY----- +# avFAerx0ZWuJE6fTI8S/0wv3yMo1n3SuNTV6zvKdxQc= +# -----END CRYPT4GH PUBLIC KEY----- +# EOF + +# chmod 444 c4gh.pub.pem + +# cat << EOF > c4gh.sec.pem +# -----BEGIN CRYPT4GH ENCRYPTED PRIVATE KEY----- +# YzRnaC12MQAGc2NyeXB0ABQAAAAAwAs5mVkXda50vqeYv6tbkQARY2hhY2hhMjBf +# cG9seTEzMDUAPAd46aTuoVWAe+fMGl3VocCKCCWmgFUsFIHejJoWxNwy62c1L/Vc +# R9haQsAPfJMLJSvUXStJ04cyZnDHSw== +# -----END CRYPT4GH ENCRYPTED PRIVATE KEY----- +# EOF + +# chmod 444 c4gh.sec.pem # insert file entry into database @@ -80,105 +80,105 @@ docker run --rm --name client --network dev_utils_default \ -t -q -c "INSERT INTO sda.file_dataset (file_id, dataset_id) \ VALUES ('$file_id', $dataset_id);" -# Make buckets if they don't exist already -s3cmd -c s3cmd-notls.conf mb s3://archive || true +# # Make buckets if they don't exist already +# s3cmd -c s3cmd-notls.conf mb s3://archive || true -# Upload test file -s3cmd -c s3cmd-notls.conf put archive_data/4293c9a7-dc50-46db-b79a-27ddc0dad1c6 s3://archive/4293c9a7-dc50-46db-b79a-27ddc0dad1c6 +# # Upload test file +# s3cmd -c s3cmd-notls.conf put archive_data/4293c9a7-dc50-46db-b79a-27ddc0dad1c6 s3://archive/4293c9a7-dc50-46db-b79a-27ddc0dad1c6 -# Test Health Endpoint -check_health=$(curl -o /dev/null -s -w "%{http_code}\n" http://localhost:8080/health) +# # Test Health Endpoint +# check_health=$(curl -o /dev/null -s -w "%{http_code}\n" http://localhost:8080/health) -if [ "$check_health" != "200" ]; then - echo "Health endpoint does not respond properly" - echo "got: ${check_health}" - exit 1 -fi +# if [ "$check_health" != "200" ]; then +# echo "Health endpoint does not respond properly" +# echo "got: ${check_health}" +# exit 1 +# fi -echo "Health endpoint is ok" +# echo "Health endpoint is ok" -# Test empty token +# # Test empty token -check_401=$(curl -o /dev/null -s -w "%{http_code}\n" -X GET http://localhost:8080/metadata/datasets) +# check_401=$(curl -o /dev/null -s -w "%{http_code}\n" -X GET http://localhost:8080/metadata/datasets) -if [ "$check_401" != "401" ]; then - echo "no token provided should give 401" - echo "got: ${check_401}" - exit 1 -fi +# if [ "$check_401" != "401" ]; then +# echo "no token provided should give 401" +# echo "got: ${check_401}" +# exit 1 +# fi -echo "got correct response when no token provided" +# echo "got correct response when no token provided" -check_405=$(curl -X POST -o /dev/null -s -w "%{http_code}\n" http://localhost:8080/metadata/datasets ) +# check_405=$(curl -X POST -o /dev/null -s -w "%{http_code}\n" http://localhost:8080/metadata/datasets ) -if [ "$check_405" != "405" ]; then - echo "POST should not be allowed" - echo "got: ${check_405}" - exit 1 -fi +# if [ "$check_405" != "405" ]; then +# echo "POST should not be allowed" +# echo "got: ${check_405}" +# exit 1 +# fi -echo "got correct response when POST method used" +# echo "got correct response when POST method used" -# Test good token +# # Test good token -token=$(curl "http://localhost:8000/tokens" | jq -r '.[0]') +# token=$(curl "http://localhost:8000/tokens" | jq -r '.[0]') -## Test datasets endpoint +# ## Test datasets endpoint -check_dataset=$(curl -H "Authorization: Bearer $token" http://localhost:8080/metadata/datasets | jq -r '.[0]') +# check_dataset=$(curl -H "Authorization: Bearer $token" http://localhost:8080/metadata/datasets | jq -r '.[0]') -if [ "$check_dataset" != "https://doi.example/ty009.sfrrss/600.45asasga" ]; then - echo "dataset https://doi.example/ty009.sfrrss/600.45asasga not found" - echo "got: ${check_dataset}" - exit 1 -fi +# if [ "$check_dataset" != "https://doi.example/ty009.sfrrss/600.45asasga" ]; then +# echo "dataset https://doi.example/ty009.sfrrss/600.45asasga not found" +# echo "got: ${check_dataset}" +# exit 1 +# fi -echo "expected dataset found" +# echo "expected dataset found" -## Test datasets/files endpoint +# ## Test datasets/files endpoint -check_files=$(curl -H "Authorization: Bearer $token" "http://localhost:8080/metadata/datasets/https://doi.example/ty009.sfrrss/600.45asasga/files" | jq -r '.[0].fileId') +# check_files=$(curl -H "Authorization: Bearer $token" "http://localhost:8080/metadata/datasets/https://doi.example/ty009.sfrrss/600.45asasga/files" | jq -r '.[0].fileId') -if [ "$check_files" != "urn:neic:001-002" ]; then - echo "file with id urn:neic:001-002 not found" - echo "got: ${check_files}" - exit 1 -fi +# if [ "$check_files" != "urn:neic:001-002" ]; then +# echo "file with id urn:neic:001-002 not found" +# echo "got: ${check_files}" +# exit 1 +# fi -echo "expected file found" +# echo "expected file found" -# Test file can be decrypted -## test also the files endpoint +# # Test file can be decrypted +# ## test also the files endpoint -C4GH_PASSPHRASE=$(grep -F passphrase config.yaml | sed -e 's/.* //' -e 's/"//g') -export C4GH_PASSPHRASE +# C4GH_PASSPHRASE=$(grep -F passphrase config.yaml | sed -e 's/.* //' -e 's/"//g') +# export C4GH_PASSPHRASE -crypt4gh decrypt --sk c4gh.sec.pem < dummy_data.c4gh > old-file.txt +# crypt4gh decrypt --sk c4gh.sec.pem < dummy_data.c4gh > old-file.txt -curl -H "Authorization: Bearer $token" "http://localhost:8080/files/urn:neic:001-002" --output test-download.txt +# curl -H "Authorization: Bearer $token" "http://localhost:8080/files/urn:neic:001-002" --output test-download.txt -cmp --silent old-file.txt test-download.txt -status=$? -if [ $status = 0 ]; then - echo "Files are the same" -else - echo "Files are different" -fi +# cmp --silent old-file.txt test-download.txt +# status=$? +# if [ $status = 0 ]; then +# echo "Files are the same" +# else +# echo "Files are different" +# fi -# Test get visas failed +# # Test get visas failed -token=$(curl "http://localhost:8000/tokens" | jq -r '.[1]') +# token=$(curl "http://localhost:8000/tokens" | jq -r '.[1]') -## Test datasets endpoint +# ## Test datasets endpoint -check_empty_token=$(curl -o /dev/null -s -w "%{http_code}\n" -H "Authorization: Bearer $token" http://localhost:8080/metadata/datasets) +# check_empty_token=$(curl -o /dev/null -s -w "%{http_code}\n" -H "Authorization: Bearer $token" http://localhost:8080/metadata/datasets) -if [ "$check_empty_token" != "200" ]; then - echo "response for empty token is not 200" - echo "got: ${check_empty_token}" - exit 1 -fi +# if [ "$check_empty_token" != "200" ]; then +# echo "response for empty token is not 200" +# echo "got: ${check_empty_token}" +# exit 1 +# fi -echo "got correct response when token has no permissions" +# echo "got correct response when token has no permissions" diff --git a/go.mod b/go.mod index b95e914..1082dca 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/go-playground/validator/v10 v10.15.2 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/golang/glog v1.1.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -69,12 +70,14 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.4.0 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.14.0 golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.13.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/grpc v1.59.0 + google.golang.org/protobuf v1.31.0 gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index ecb590c..7a2bab0 100644 --- a/go.sum +++ b/go.sum @@ -138,6 +138,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -597,6 +599,8 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -613,6 +617,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -624,6 +630,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/config/config.go b/internal/config/config.go index debbb9a..e38a852 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -34,6 +34,7 @@ type Map struct { DB DatabaseConfig OIDC OIDCConfig Archive storage.Conf + Grpc GrpcConfig } type AppConfig struct { @@ -62,6 +63,10 @@ type AppConfig struct { Middleware string } +type GrpcConfig struct { + AppConfig +} + type SessionConfig struct { // Session key expiration time in seconds. // Optional. Default value -1 @@ -139,7 +144,9 @@ type DatabaseConfig struct { } // NewConfig populates ConfigMap with data -func NewConfig() (*Map, error) { +func NewConfig(app string) (*Map, error) { + var requiredConfVars []string + viper.SetConfigName("config") viper.AddConfigPath(".") viper.AutomaticEnv() @@ -162,21 +169,6 @@ func NewConfig() (*Map, error) { return nil, err } } - requiredConfVars := []string{ - "db.host", "db.user", "db.password", "db.database", "c4gh.filepath", "c4gh.passphrase", "oidc.configuration.url", - } - - if viper.GetString("archive.type") == S3 { - requiredConfVars = append(requiredConfVars, []string{"archive.url", "archive.accesskey", "archive.secretkey", "archive.bucket"}...) - } else if viper.GetString("archive.type") == POSIX { - requiredConfVars = append(requiredConfVars, []string{"archive.location"}...) - } - - for _, s := range requiredConfVars { - if !viper.IsSet(s) || viper.GetString(s) == "" { - return nil, fmt.Errorf("%s not set", s) - } - } if viper.IsSet("log.format") { if viper.GetString("log.format") == "json" { @@ -196,22 +188,60 @@ func NewConfig() (*Map, error) { log.Printf("Setting log level to '%s'", stringLevel) } - c := &Map{} - c.applyDefaults() - c.sessionConfig() - c.configArchive() - err := c.configureOIDC() - if err != nil { - return nil, err + switch app { + case "download": + requiredConfVars = []string{ + "db.host", "db.user", "db.password", "db.database", "c4gh.filepath", "c4gh.passphrase", "oidc.configuration.url", + } + case "reencrypt": + requiredConfVars = []string{ + "db.host", "db.user", "db.password", "db.database", "c4gh.filepath", "c4gh.passphrase", + } + default: + requiredConfVars = []string{ + "db.host", "db.user", "db.password", "db.database", "c4gh.filepath", "c4gh.passphrase", "oidc.configuration.url", + } } - err = c.appConfig() - if err != nil { - return nil, err + + if viper.GetString("archive.type") == S3 { + requiredConfVars = append(requiredConfVars, []string{"archive.url", "archive.accesskey", "archive.secretkey", "archive.bucket"}...) + } else if viper.GetString("archive.type") == POSIX { + requiredConfVars = append(requiredConfVars, []string{"archive.location"}...) } - err = c.configDatabase() - if err != nil { - return nil, err + for _, s := range requiredConfVars { + if !viper.IsSet(s) || viper.GetString(s) == "" { + return nil, fmt.Errorf("%s not set", s) + } + } + c := &Map{} + c.applyDefaults() + switch app { + case "download": + c.sessionConfig() + c.configArchive() + err := c.configureOIDC() + if err != nil { + return nil, err + } + err = c.appConfig() + if err != nil { + return nil, err + } + + err = c.configDatabase() + if err != nil { + return nil, err + } + case "reencrypt": + err := c.configDatabase() + if err != nil { + return nil, err + } + err = c.grpcServerConfig() + if err != nil { + return nil, err + } } return c, nil @@ -222,6 +252,8 @@ func NewConfig() (*Map, error) { func (c *Map) applyDefaults() { viper.SetDefault("app.host", "0.0.0.0") viper.SetDefault("app.port", 8080) + viper.SetDefault("grpc.server.host", "0.0.0.0") + viper.SetDefault("grpc.server.port", 5051) viper.SetDefault("app.middleware", "default") viper.SetDefault("session.expiration", -1) viper.SetDefault("session.secure", true) @@ -325,6 +357,28 @@ func (c *Map) appConfig() error { return nil } +// grpc-server sets required settings +func (c *Map) grpcServerConfig() error { + c.Grpc.Host = viper.GetString("grpc.server.host") + c.Grpc.Port = viper.GetInt("grpc.server.port") + c.Grpc.ServerCert = viper.GetString("grpc.server.servercert") + c.Grpc.ServerKey = viper.GetString("grpc.server.serverkey") + + if c.Grpc.Port != 443 && c.Grpc.Port != 5051 { + c.Grpc.Port = viper.GetInt("grpc.server.port") + } else if c.Grpc.ServerCert != "" && c.App.ServerKey != "" { + c.Grpc.Port = 443 + } + + var err error + c.Grpc.Crypt4GHKey, err = GetC4GHKey() + if err != nil { + return err + } + + return nil +} + // sessionConfig controls cookie settings and session cache func (c *Map) sessionConfig() { c.Session.Expiration = time.Duration(viper.GetInt("session.expiration")) * time.Second diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 0fe37ac..cd2538b 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -41,7 +41,7 @@ func TestConfigTestSuite(t *testing.T) { func (suite *TestSuite) TestConfigFile() { viper.Set("configFile", "test") - config, err := NewConfig() + config, err := NewConfig("download") assert.Nil(suite.T(), config) assert.Error(suite.T(), err) assert.Equal(suite.T(), "test", viper.ConfigFileUsed()) @@ -52,7 +52,7 @@ func (suite *TestSuite) TestMissingRequiredConfVar() { requiredConfVarValue := viper.Get(requiredConfVar) viper.Set(requiredConfVar, nil) expectedError := fmt.Errorf("%s not set", requiredConfVar) - config, err := NewConfig() + config, err := NewConfig("download") assert.Nil(suite.T(), config) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), expectedError, err) diff --git a/internal/reencrypt/reencrypt.pb.go b/internal/reencrypt/reencrypt.pb.go new file mode 100644 index 0000000..d35023f --- /dev/null +++ b/internal/reencrypt/reencrypt.pb.go @@ -0,0 +1,229 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.6.1 +// source: internal/reencrypt/reencrypt.proto + +package reencrypt + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// The request message containing the required input +type ReencryptRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Publickey string `protobuf:"bytes,1,opt,name=publickey,proto3" json:"publickey,omitempty"` + Fileid string `protobuf:"bytes,2,opt,name=fileid,proto3" json:"fileid,omitempty"` +} + +func (x *ReencryptRequest) Reset() { + *x = ReencryptRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_reencrypt_reencrypt_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReencryptRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReencryptRequest) ProtoMessage() {} + +func (x *ReencryptRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_reencrypt_reencrypt_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReencryptRequest.ProtoReflect.Descriptor instead. +func (*ReencryptRequest) Descriptor() ([]byte, []int) { + return file_internal_reencrypt_reencrypt_proto_rawDescGZIP(), []int{0} +} + +func (x *ReencryptRequest) GetPublickey() string { + if x != nil { + return x.Publickey + } + return "" +} + +func (x *ReencryptRequest) GetFileid() string { + if x != nil { + return x.Fileid + } + return "" +} + +// The response message containing the re-encrypted header +type ReencryptResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Header []byte `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` +} + +func (x *ReencryptResponse) Reset() { + *x = ReencryptResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_reencrypt_reencrypt_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReencryptResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReencryptResponse) ProtoMessage() {} + +func (x *ReencryptResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_reencrypt_reencrypt_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReencryptResponse.ProtoReflect.Descriptor instead. +func (*ReencryptResponse) Descriptor() ([]byte, []int) { + return file_internal_reencrypt_reencrypt_proto_rawDescGZIP(), []int{1} +} + +func (x *ReencryptResponse) GetHeader() []byte { + if x != nil { + return x.Header + } + return nil +} + +var File_internal_reencrypt_reencrypt_proto protoreflect.FileDescriptor + +var file_internal_reencrypt_reencrypt_proto_rawDesc = []byte{ + 0x0a, 0x22, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x72, 0x65, 0x65, 0x6e, 0x63, + 0x72, 0x79, 0x70, 0x74, 0x2f, 0x72, 0x65, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x72, 0x65, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x22, + 0x48, 0x0a, 0x10, 0x52, 0x65, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x6b, 0x65, + 0x79, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x65, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x65, 0x69, 0x64, 0x22, 0x2b, 0x0a, 0x11, 0x52, 0x65, 0x65, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, + 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x32, 0x5b, 0x0a, 0x09, 0x52, 0x65, 0x65, 0x6e, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x12, 0x4e, 0x0a, 0x0f, 0x52, 0x65, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1b, 0x2e, 0x72, 0x65, 0x65, 0x6e, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x2e, 0x52, 0x65, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x72, 0x65, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, + 0x52, 0x65, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x6e, 0x65, 0x69, 0x63, 0x6e, 0x6f, 0x72, 0x64, 0x69, 0x63, 0x2f, 0x73, 0x64, 0x61, + 0x2d, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2f, 0x72, 0x65, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_internal_reencrypt_reencrypt_proto_rawDescOnce sync.Once + file_internal_reencrypt_reencrypt_proto_rawDescData = file_internal_reencrypt_reencrypt_proto_rawDesc +) + +func file_internal_reencrypt_reencrypt_proto_rawDescGZIP() []byte { + file_internal_reencrypt_reencrypt_proto_rawDescOnce.Do(func() { + file_internal_reencrypt_reencrypt_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_reencrypt_reencrypt_proto_rawDescData) + }) + return file_internal_reencrypt_reencrypt_proto_rawDescData +} + +var file_internal_reencrypt_reencrypt_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_internal_reencrypt_reencrypt_proto_goTypes = []interface{}{ + (*ReencryptRequest)(nil), // 0: reencrypt.ReencryptRequest + (*ReencryptResponse)(nil), // 1: reencrypt.ReencryptResponse +} +var file_internal_reencrypt_reencrypt_proto_depIdxs = []int32{ + 0, // 0: reencrypt.Reencrypt.ReencryptHeader:input_type -> reencrypt.ReencryptRequest + 1, // 1: reencrypt.Reencrypt.ReencryptHeader:output_type -> reencrypt.ReencryptResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_internal_reencrypt_reencrypt_proto_init() } +func file_internal_reencrypt_reencrypt_proto_init() { + if File_internal_reencrypt_reencrypt_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_internal_reencrypt_reencrypt_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReencryptRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_internal_reencrypt_reencrypt_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReencryptResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_internal_reencrypt_reencrypt_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_internal_reencrypt_reencrypt_proto_goTypes, + DependencyIndexes: file_internal_reencrypt_reencrypt_proto_depIdxs, + MessageInfos: file_internal_reencrypt_reencrypt_proto_msgTypes, + }.Build() + File_internal_reencrypt_reencrypt_proto = out.File + file_internal_reencrypt_reencrypt_proto_rawDesc = nil + file_internal_reencrypt_reencrypt_proto_goTypes = nil + file_internal_reencrypt_reencrypt_proto_depIdxs = nil +} diff --git a/internal/reencrypt/reencrypt.proto b/internal/reencrypt/reencrypt.proto new file mode 100644 index 0000000..d471929 --- /dev/null +++ b/internal/reencrypt/reencrypt.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +option go_package = "github.com/neicnordic/sda-download/internal/reencrypt"; + +package reencrypt; + +// The Reencrypt service definition. +service Reencrypt { + // Sends the re-encrypted header back + rpc ReencryptHeader (ReencryptRequest) returns (ReencryptResponse) {} +} + +// The request message containing the required input +message ReencryptRequest { + string publickey = 1; + string fileid = 2; +} + +// The response message containing the re-encrypted header +message ReencryptResponse { + bytes header = 1; +} diff --git a/internal/reencrypt/reencrypt_grpc.pb.go b/internal/reencrypt/reencrypt_grpc.pb.go new file mode 100644 index 0000000..ae7bb54 --- /dev/null +++ b/internal/reencrypt/reencrypt_grpc.pb.go @@ -0,0 +1,107 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.6.1 +// source: internal/reencrypt/reencrypt.proto + +package reencrypt + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ReencryptClient is the client API for Reencrypt service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ReencryptClient interface { + // Sends the re-encrypted header back + ReencryptHeader(ctx context.Context, in *ReencryptRequest, opts ...grpc.CallOption) (*ReencryptResponse, error) +} + +type reencryptClient struct { + cc grpc.ClientConnInterface +} + +func NewReencryptClient(cc grpc.ClientConnInterface) ReencryptClient { + return &reencryptClient{cc} +} + +func (c *reencryptClient) ReencryptHeader(ctx context.Context, in *ReencryptRequest, opts ...grpc.CallOption) (*ReencryptResponse, error) { + out := new(ReencryptResponse) + err := c.cc.Invoke(ctx, "/reencrypt.Reencrypt/ReencryptHeader", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ReencryptServer is the server API for Reencrypt service. +// All implementations must embed UnimplementedReencryptServer +// for forward compatibility +type ReencryptServer interface { + // Sends the re-encrypted header back + ReencryptHeader(context.Context, *ReencryptRequest) (*ReencryptResponse, error) + mustEmbedUnimplementedReencryptServer() +} + +// UnimplementedReencryptServer must be embedded to have forward compatible implementations. +type UnimplementedReencryptServer struct { +} + +func (UnimplementedReencryptServer) ReencryptHeader(context.Context, *ReencryptRequest) (*ReencryptResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReencryptHeader not implemented") +} +func (UnimplementedReencryptServer) mustEmbedUnimplementedReencryptServer() {} + +// UnsafeReencryptServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ReencryptServer will +// result in compilation errors. +type UnsafeReencryptServer interface { + mustEmbedUnimplementedReencryptServer() +} + +func RegisterReencryptServer(s grpc.ServiceRegistrar, srv ReencryptServer) { + s.RegisterService(&Reencrypt_ServiceDesc, srv) +} + +func _Reencrypt_ReencryptHeader_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReencryptRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReencryptServer).ReencryptHeader(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/reencrypt.Reencrypt/ReencryptHeader", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReencryptServer).ReencryptHeader(ctx, req.(*ReencryptRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Reencrypt_ServiceDesc is the grpc.ServiceDesc for Reencrypt service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Reencrypt_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "reencrypt.Reencrypt", + HandlerType: (*ReencryptServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ReencryptHeader", + Handler: _Reencrypt_ReencryptHeader_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "internal/reencrypt/reencrypt.proto", +}