Skip to content

[full-ci] feat: ocisdev-533 graph#12108

Open
2403905 wants to merge 5 commits intomasterfrom
feat/OCISDEV-533-graph
Open

[full-ci] feat: ocisdev-533 graph#12108
2403905 wants to merge 5 commits intomasterfrom
feat/OCISDEV-533-graph

Conversation

@2403905
Copy link
Copy Markdown
Contributor

@2403905 2403905 commented Mar 12, 2026

Description

Provide the separated vault storage that could be MFA-protected

Implementation approach:

Provide the dedicated storage-users and graph service to care only about vault storage.

  • Provide the new vault user storage with a dedicated VaultStorageProviderID mounted to "/vault/users" and "/vault/projects".
  • Teach the storage registry to associate the vault storage with the dedicated storage-users service.
  • Modify the graph service to force using StorageProviderID in a vault-mode. Run in addition the graph API serves the /vault prefix.
  • Run the dedicated storage-users service pointed to the vault.

Related reva PR owncloud/reva#559

How to run in a Docker

UPD: 13.04.2026

  • build the dev docker image
  • goto ./deployments/examples/ocis_full
  • in a .env uncomment the line KEYCLOAK=:keycloak.yml and VAULT_STORAGE=:vault-storage.yml
  • run the docker compose IDM_ADMIN_PASSWORD=admin DEMO_USERS=true OCIS_DOCKER_TAG=dev OCIS_MFA_ENABLED=true docker compose up -d

How to run locally

UPD: 18.03.2026 - No extra graph service needed
ocis main

WEB_ASSET_CORE_PATH=/Users/roman/projects/owncloud-extra/web/dist \
OCIS_MFA_ENABLED=true \
PROXY_CREATE_VAULT_HOME=true \
GRAPH_ENABLE_VAULT_MODE=true \
IDM_ADMIN_PASSWORD=admin IDM_CREATE_DEMO_USERS=true PROXY_ENABLE_BASIC_AUTH=true OCIS_LOG_LEVEL=info ./ocis/bin/ocis server

In a Keycloak setup set to trueOCIS_MFA_ENABLED
WEB_ASSET_CORE_PATH={path to web}/web/dist \

Vault storage-users

OCIS_LOG_LEVEL=debug \
STORAGE_USERS_ENABLE_VAULT_MODE=true \
STORAGE_USERS_SERVICE_NAME=storage-users-vault \
STORAGE_USERS_GRPC_ADDR=0.0.0.0:9170 \
STORAGE_USERS_HTTP_ADDR=0.0.0.0:9168 \
STORAGE_USERS_DATA_SERVER_URL=http://localhost:9168/data \
STORAGE_USERS_DEBUG_ADDR=0.0.0.0:9169 \
STORAGE_USERS_OCIS_ROOT=${HOME}/.ocis/storage/users-vault \
STORAGE_USERS_EVENTS_CONSUMER_GROUP=vault-dcfs \
./ocis/bin/ocis storage-users server
curl 'https://localhost:9200/vault/graph/v1beta1/drives' \
-H 'Accept: application/json' \
--data-raw '{"name":"vault Space"}' \
--insecure  -uadmin:admin

curl 'https://localhost:9200/vault/graph/v1beta1/me/drives?%24orderby=name+asc&%24filter=driveType+eq+project' \
-H 'Accept: application/json' \
--insecure  -uadmin:admin

curl 'https://localhost:9200/vault/graph/v1beta1/me/drives?%24orderby=name+asc&%24filter=driveType+eq+personal' \
-H 'Accept: application/json' \
--insecure  -uadmin:admin

FS:

~/.ocis/storage
$ tree  users*
users
├── indexes
│   ├── by-group-id
│   ├── by-type
│   │   └── personal.mpk
│   └── by-user-id
│       └── a032f2bd-fa5c-430b-a163-2c19f54190d0.mpk
├── spaces
│   └── a0
│       └── 32f2bd-fa5c-430b-a163-2c19f54190d0
│           └── nodes
│               └── a0
│                   └── 32
│                       └── f2
│                           └── bd
│                               ├── -fa5c-430b-a163-2c19f54190d0
│                               ├── -fa5c-430b-a163-2c19f54190d0.mlock
│                               └── -fa5c-430b-a163-2c19f54190d0.mpk
└── uploads
users-vault
├── indexes
│   ├── by-group-id
│   ├── by-type
│   │   └── personal.mpk
│   └── by-user-id
│       └── a032f2bd-fa5c-430b-a163-2c19f54190d0.mpk
├── spaces
│   └── a0
│       └── 32f2bd-fa5c-430b-a163-2c19f54190d0
│           └── nodes
│               └── a0
│                   └── 32
│                       └── f2
│                           └── bd
│                               ├── -fa5c-430b-a163-2c19f54190d0
│                               ├── -fa5c-430b-a163-2c19f54190d0.mlock
│                               └── -fa5c-430b-a163-2c19f54190d0.mpk
└── uploads

@update-docs
Copy link
Copy Markdown

update-docs Bot commented Mar 12, 2026

Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would create a changelog item based on your changes.

@2403905 2403905 requested review from jvillafanez and kobergj March 12, 2026 11:15
@2403905 2403905 force-pushed the feat/OCISDEV-533-graph branch from 96ce268 to c79f474 Compare March 12, 2026 11:31
Comment thread services/graph/pkg/config/config.go Outdated
@jvillafanez
Copy link
Copy Markdown
Member

There are a couple of things that seems weird to me:

  • I understand that we can setup a storage-user service in "vault mode" so it's only accessible / usable with MFA, but why graph?
    • The graph service can just check if the request is under MFA to do anything with the vault.
  • Using mount ids in the configuration isn't user-friendly. If possible, I'd drop them, otherwise they MUST be documented, including where such id can be found and / or provide a command to get the id.
    • Note that it's easy to make typos and setup a mount id that doesn't exist. And people makes mistakes and might use any random id found anywhere, so it might point to who-knows-what.

@2403905
Copy link
Copy Markdown
Contributor Author

2403905 commented Mar 12, 2026

There are a couple of things that seems weird to me:

  • I understand that we can setup a storage-user service in "vault mode" so it's only accessible / usable with MFA, but why graph?

Maybe we can improve it and use only one graph. Now I use the second one for enforcing the vault storage and MFA for all graph endpoints

  • Note that it's easy to make typos and setup a mount id that doesn't exist. And people makes mistakes and might use any random id found anywhere, so it might point to who-knows-what.

Ideally, we could try to add one more storageprovider in a config and get rid of the dedicated storage-users service.

"services": map[string]interface{}{
"storageprovider": map[string]interface{}{
"driver": cfg.Driver,
"drivers": StorageProviderDrivers(cfg),
"mount_id": cfg.MountID,
"expose_data_server": cfg.ExposeDataServer,
"data_server_url": cfg.DataServerURL,
"upload_expiration": cfg.UploadExpiration,
"events": map[string]interface{}{
"nats_address": cfg.Events.Addr,
"nats_clusterid": cfg.Events.ClusterID,
"tls_insecure": cfg.Events.TLSInsecure,
"tls_root_ca_cert": cfg.Events.TLSRootCaCertPath,
"nats_enable_tls": cfg.Events.EnableTLS,
"nats_username": cfg.Events.AuthUsername,
"nats_password": cfg.Events.AuthPassword,
},
},
},
"interceptors": map[string]interface{}{

Thank you.

@jvillafanez
Copy link
Copy Markdown
Member

Maybe we can improve it and use only one graph. Now I use the second one for enforcing the vault storage and MFA for all graph endpoints

MFA needs to be enforced in the vault storage. Technically, graph shouldn't need to enforce MFA; it can make the request and the request will fail. The fact that we want graph to check for MFA is mostly for convenience, to avoid making a request that we know it will fail.

In addition, whether the request is under MFA or not is information that should be part of the request, and should be propagated as part of the request. This is very similar to what is done with telemetry. Graph shouldn't need a configuration flag to know if it can access to the vault or if MFA is active.

@2403905 2403905 changed the title Feat/ocisdev 533 graph [full-ci] feat: ocisdev-533 graph Mar 14, 2026
@2403905 2403905 requested review from Copilot and mklos-kw March 16, 2026 14:33
@2403905 2403905 force-pushed the feat/OCISDEV-533-graph branch from df07882 to a9fa2c1 Compare March 16, 2026 14:35
@2403905 2403905 marked this pull request as ready for review March 16, 2026 14:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the vendored reva dependency and introduces “vault mode” support across gateway, storage-users, proxy, and graph, including storage scoping for events and storage space selection.

Changes:

  • Bump github.com/owncloud/reva/v2 vendor version and adapt code to upstream changes (events, storage registry filtering, gateway client acquisition).
  • Add vault-mode plumbing: vault storage provider IDs/constants, vault-specific space provider config, and passing storage_id via CS3 opaque to target the vault storage.
  • Add configurable event consumer group and storage-aware filtering for decomposedfs postprocessing events; add MFA middleware integration for graph routes.

Reviewed changes

Copilot reviewed 18 out of 34 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
vendor/modules.txt Updates vendored module version for reva/v2.
vendor/github.com/owncloud/reva/v2/pkg/utils/utils.go Adds vault storage provider/space IDs.
vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/options/options.go Adds consumer_group event option + default.
vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go Uses configurable consumer group; filters events by storage.
vendor/github.com/owncloud/reva/v2/pkg/storage/registry/spaces/spaces.go Filters vault spaces unless explicitly requested; supports storage_id filter.
vendor/github.com/owncloud/reva/v2/pkg/storage/cache/createpersonalspace.go Removes create-personal-space cache implementation.
vendor/github.com/owncloud/reva/v2/pkg/storage/cache/createhome.go Removes create-home cache implementation.
vendor/github.com/owncloud/reva/v2/pkg/storage/cache/cache.go Removes create-home/create-personal-space cache APIs.
vendor/github.com/owncloud/reva/v2/pkg/events/postprocessing.go Adds ResourceID to postprocessing events.
vendor/github.com/owncloud/reva/v2/internal/http/.../shares/spaces.go Simplifies provider client creation via pool directly.
vendor/github.com/owncloud/reva/v2/internal/grpc/services/storageprovider/storageprovider.go Ensures root-info IDs get storage provider IDs filled.
vendor/github.com/owncloud/reva/v2/internal/grpc/services/gateway/storageprovidercache.go Adjusts cache keying to include storage_id (opaque).
vendor/github.com/owncloud/reva/v2/internal/grpc/services/gateway/storageprovider.go Forwards storage_id via opaque and provider selection; removes some caching wrappers.
vendor/github.com/owncloud/reva/v2/internal/grpc/services/gateway/gateway.go Removes create-personal-space cache wiring.
services/storage-users/pkg/revaconfig/drivers.go Passes consumer_group to reva storage config.
services/storage-users/pkg/config/defaults/defaultconfig.go Sets MountID to vault provider ID when vault mode enabled.
services/storage-users/pkg/config/config.go Adds enable_vault_mode and consumer_group config fields.
services/proxy/pkg/middleware/create_home.go Adds short-lived in-process TTL cache; creates regular + vault homes via storage_id.
services/proxy/pkg/config/defaults/defaultconfig.go Adds proxy policy route for /vault/graph/.
services/postprocessing/pkg/postprocessing/postprocessing.go Propagates ResourceID into emitted events.
services/policies/pkg/service/event/service.go Propagates ResourceID into emitted events.
services/graph/pkg/service/v0/service.go Adds RequireMFA middleware and vault-mode MFA routing behavior.
services/graph/pkg/service/v0/graph_test.go Updates tests to exercise router (ServeHTTP) instead of direct handler calls.
services/graph/pkg/service/v0/drives.go Forces vault storage selection via storage_id in opaque; removes inline MFA checks.
services/graph/pkg/service/v0/driveitems.go Adds vault-mode filtering for personal root children.
services/graph/pkg/middleware/mfa.go New middleware to enforce MFA.
services/graph/pkg/config/service.go Adds env/yaml tags for service name.
services/graph/pkg/config/config.go Adds enable_vault_mode config field.
services/gateway/pkg/revaconfig/config.go Adds a dedicated vault spaces provider definition.
services/gateway/pkg/config/defaults/defaultconfig.go Removes create-home cache defaults.
services/gateway/pkg/config/config.go Removes create-home cache configuration fields.
go.mod / go.sum Updates reva/v2 dependency version checksums.
.gitignore Ignores .agents/ directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread services/proxy/pkg/middleware/create_home.go Outdated
Comment thread services/graph/pkg/service/v0/driveitems.go Outdated
Comment thread services/graph/pkg/config/config.go Outdated
Comment thread services/graph/pkg/config/service.go Outdated
Comment thread services/storage-users/pkg/config/config.go Outdated
Comment thread services/storage-users/pkg/config/config.go Outdated
@2403905
Copy link
Copy Markdown
Contributor Author

2403905 commented Mar 17, 2026

Maybe we can improve it and use only one graph. Now I use the second one for enforcing the vault storage and MFA for all graph endpoints

MFA needs to be enforced in the vault storage. Technically, graph shouldn't need to enforce MFA; it can make the request and the request will fail. The fact that we want graph to check for MFA is mostly for convenience, to avoid making a request that we know it will fail.

In addition, whether the request is under MFA or not is information that should be part of the request, and should be propagated as part of the request. This is very similar to what is done with telemetry. Graph shouldn't need a configuration flag to know if it can access to the vault or if MFA is active.

That is a good point. @jvillafanez
The idea is to implement the Vault that doesn't require MFA dependencies.
The Vault-graph should ensure that requests go to Vault storage by requiring that some requests, like CreateStorageSpace and GetAllDrives, use the Vault storage ID explicitly.
So, the Vault graph can work without MFA, ensuring the drives' separation.
Then I assumed that the Graph is a good place to force MFA for all of the routes under the /vault with minimal effort.
The cons of this we still need to take care of the ocdav api MFA protection.

We can get rid of the extra vault-graph instance to provide an additional router that could be above the MFA

@2403905
Copy link
Copy Markdown
Contributor Author

2403905 commented Mar 17, 2026

@jvillafanez @mklos-kw I made a commit that makes the graph able to handle the vault prefix. no extra service needed
d456f68

@2403905 2403905 force-pushed the feat/OCISDEV-533-graph branch 2 times, most recently from b53fb87 to 5fc45db Compare March 18, 2026 08:53
@mmattel
Copy link
Copy Markdown
Contributor

mmattel commented Mar 18, 2026

The envvar description text says the following:
"GRAPH_ENABLE_VAULT_MODE" desc:"Enable vault mode for the graph service running in addition to the regular graph service. Requires running the storage-users-vault additional service."

This part confuses me: Requires running the storage-users-vault additional service.
We have an additional service (which I cant identify) or just an instance of the storage-users service with a different config? (a config example (or reference) should be added e.g. in the storage-users readme)

The envvar text should be precised (or the missing service added).

@2403905 2403905 force-pushed the feat/OCISDEV-533-graph branch 3 times, most recently from 8fcac29 to f9c326c Compare March 19, 2026 18:16
@2403905
Copy link
Copy Markdown
Contributor Author

2403905 commented Mar 20, 2026

The envvar description text says the following: "GRAPH_ENABLE_VAULT_MODE" desc:"Enable vault mode for the graph service running in addition to the regular graph service. Requires running the storage-users-vault additional service."

This part confuses me: Requires running the storage-users-vault additional service. We have an additional service (which I cant identify) or just an instance of the storage-users service with a different config? (a config example (or reference) should be added e.g. in the storage-users readme)

The envvar text should be precised (or the missing service added).

For now, the Vault mode requires running the storage-users as an additional service with specific configuration

@mmattel
Copy link
Copy Markdown
Contributor

mmattel commented Mar 20, 2026

For now, the Vault mode requires running the storage-users as an additional service with specific configuration

OK, makes sense. Please rewrite the envvar text accordingly because the current one does not match.

@2403905 2403905 force-pushed the feat/OCISDEV-533-graph branch from 85d7fdb to 702c7c7 Compare March 23, 2026 09:54
@jvillafanez
Copy link
Copy Markdown
Member

Then I assumed that the Graph is a good place to force MFA for all of the routes under the /vault with minimal effort.
The cons of this we still need to take care of the ocdav api MFA protection.

MFA needs to be enforced in the vault. Anyone can access to the vault / storage_users using GRPC, and that will bypass the enforcement.
If it's enforced in the vault, at service level, it doesn't matter how the user access (HTTP or GRPC) because he must be under MFA.

I think the problem is that the MFA data isn't propagated among the services. It "works" with graph because there are several direct requests from web to access to the graph service, so the information is there, but since the storage_users service is usually behind and accessed via GRPC, web won't access to it directly. Due to the missing propagation, the storage_users service can't check if the user is under MFA or not because it doesn't have such info.
Fixing the data propagation would allow us to check if the request is under MFA anywhere, in particular the storage_users service.

For the office apps, note that the requests come from an external service, so they won't have MFA as such. I assume this is another reason for you to enforce MFA in the graph, but technically you're accessing to the vault without MFA. I'd need to check, but it might be possible to open "vaulted" files with an office app without being under MFA.
In order to fix this issue, if the MFA data is propagated, the collaboration service can include MFA information in the access token for the office app (I think this will need to be done manually), so when the office app makes a request with such access token, the collaboration service can get the MFA information from the access token and decide to fetch the "vaulted" file or not (or just send the request with the MFA info).

@jvillafanez
Copy link
Copy Markdown
Member

Something to check:

{"level":"debug","service":"storage-users-vault","service":{"name":"com.owncloud.api.storage-users-vault","version":"8.0.0+dddd84ed377","metadata":null,"endpoints":[],"nodes":[{"metadata":{"protocol":"grpc","registry":"cache","server":"grpc","transport":"tcp"},"id":"com.owncloud.api.storage-users-vault-a00d3ef7-85af-4f3b-8359-d15d406ca983","address":"storage-users-vault:9170"}]},"time":"2026-03-31T16:25:45Z","line":"/home/juan/src/ocis/ocis/ocis-pkg/registry/register.go:43","message":"refreshing external service-registration"}
{"level":"error","event":"PostprocessingFinished","uploadid":"a1233395-bb1f-4f8a-89db-fcba11ea90de","error":"ERR_UPLOAD_NOT_FOUND: upload not found","time":"2026-03-31T16:25:57Z","message":"Failed to get upload"}
{"level":"debug","service":"storage-users-vault","pkg":"rgrpc","traceid":"1333eed937a481390175eefd0bc6e0f4","user-agent":"grpc-go/1.79.1","from":"tcp://172.25.0.5:38824","uri":"/cs3.storage.provider.v1beta1.SpacesAPI/CreateStorageSpace","start":"31/Mar/2026:16:25:57 +0000","end":"31/Mar/2026:16:25:57 +0000","time_ns":1145277,"code":"OK","time":"2026-03-31T16:25:57Z","line":"/home/juan/src/ocis/ocis/vendor/github.com/owncloud/reva/v2/internal/grpc/interceptors/log/log.go:69","message":"unary"}

That's an error coming from the storage-users-vault after uploading a 1GB file into the regular personal storage.

As far as I know, there are several events being triggered during the file upload, and some of them are being listened by the storage-user service. We usually have only one instance of the service running, so there is no problem, but with the storage-user-vault we'll have an additional instance.
There is configuration in place to setup a different event group for the storage. This is fine, but this means that the regular storage-user and the storage-user-vault instance will receive each one a copy of the event. This seems to cause the seemingly harmless error message in the vault storage because the file is missing there (it's in the regular storage).

I think there are a couple of things to do in this regard:

  • Document the available setups:
    • Only one storage-users service -> no need to change the event group. Files can be stored in its local FS.
    • Multiple storage-users service replicas -> event group will be required (must be the same for all the replicas) AND ALSO IMPORTANT, the FS must be shared among all the replicas (likely via NFS, maybe S3...). This will need additional setup so I wouldn't recommend this setup.
    • With storage vault -> we need a different event group to ensure events are received by the storage vault, otherwise the events might be processed by the regular storage. In case of having replicas of the vault, we need to follow the same approach as with the replicas of the storage-users service (previous point).
  • We'll need to figure out a way to deal with the error above:
    • The log will likely be annoying (I think it happens on any upload), and ignoring it might hide an actual problem.
    • Suppressing the error will hide the problem.
    • If the storage-users service can identify the event it know it's the storage-users service the one that should be processing the event and not vault, then the vault could safely ignore the event because it isn't for it.

@jvillafanez
Copy link
Copy Markdown
Member

@2403905 a couple of changes to consider:

diff --git a/services/storage-users/pkg/revaconfig/drivers.go b/services/storage-users/pkg/revaconfig/drivers.go
index 9d6da1c1191..66c932139be 100644
--- a/services/storage-users/pkg/revaconfig/drivers.go
+++ b/services/storage-users/pkg/revaconfig/drivers.go
@@ -157,6 +157,7 @@ func OwnCloudSQL(cfg *config.Config) map[string]interface{} {
 // Ocis is the config mapping for the Ocis storage driver
 func Ocis(cfg *config.Config) map[string]interface{} {
        return map[string]interface{}{
+               "mount_id":         cfg.MountID,
                "metadata_backend": "messagepack",
                "propagator":       cfg.Drivers.OCIS.Propagator,
                "async_propagator_options": map[string]interface{}{
diff --git a/vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/upload/upload.go b/vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/upload/upload.go
index 97b506d855c..6e91c8a409f 100644
--- a/vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/upload/upload.go
+++ b/vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/upload/upload.go
@@ -224,7 +224,7 @@ func (session *OcisSession) FinishUploadDecomposed(ctx context.Context) error {
                        URL:               s,
                        SpaceOwner:        n.SpaceOwnerOrManager(session.Context(ctx)),
                        ExecutingUser:     u,
-                       ResourceID:        &provider.ResourceId{SpaceId: n.SpaceID, OpaqueId: n.ID},
+                       ResourceID:        &provider.ResourceId{StorageId: session.ProviderID(), SpaceId: n.SpaceID, OpaqueId: n.ID},
                        Filename:          session.Filename(),
                        Filesize:          uint64(session.Size()),
                        ImpersonatingUser: iu,

Both storageID and mountID are missing, so when processing the event it goes through the "upload not found" path instead of the expected "ignored event". The patch above should fix this.
We might need to consider to include the "mount_id" in each of the storage drivers, not just ocis.
In addition, we might need to include the storageId in other places taking into account that the storageId is supposed to be required, and I think I've seen some places where the ResourceId isn't filled with a storageId.

That should fix my previous comment (#12108 (comment)) since the rest of the code is already in place.
We still need to document the setups though.

@2403905 2403905 force-pushed the feat/OCISDEV-533-graph branch from dddd84e to b7e39bc Compare April 7, 2026 17:26
@2403905
Copy link
Copy Markdown
Contributor Author

2403905 commented Apr 7, 2026

@2403905 a couple of changes to consider:
....

Thanks. Fixed.

@2403905 2403905 force-pushed the feat/OCISDEV-533-graph branch from b7e39bc to f4d83bc Compare April 8, 2026 16:35
@2403905
Copy link
Copy Markdown
Contributor Author

2403905 commented Apr 9, 2026

MFA needs to be enforced in the vault. Anyone can access to the vault / storage_users using GRPC, and that will bypass the enforcement. If it's enforced in the vault, at service level, it doesn't matter how the user access (HTTP or GRPC) because he must be under MFA.

I think the problem is that the MFA data isn't propagated among the services. It "works" with graph because there are several direct requests from web to access to the graph service, so the information is there, but since the storage_users service is usually behind and accessed via GRPC, web won't access to it directly. Due to the missing propagation, the storage_users service can't check if the user is under MFA or not because it doesn't have such info. Fixing the data propagation would allow us to check if the request is under MFA anywhere, in particular the storage_users service.

For the office apps, note that the requests come from an external service, so they won't have MFA as such. I assume this is another reason for you to enforce MFA in the graph, but technically you're accessing to the vault without MFA. I'd need to check, but it might be possible to open "vaulted" files with an office app without being under MFA. In order to fix this issue, if the MFA data is propagated, the collaboration service can include MFA information in the access token for the office app (I think this will need to be done manually), so when the office app makes a request with such access token, the collaboration service can get the MFA information from the access token and decide to fetch the "vaulted" file or not (or just send the request with the MFA info).

@jvillafanez This is a very good point. We need to figure out how to propagate the MFA. Can we add it to the "tokenScope"?

@2403905 2403905 force-pushed the feat/OCISDEV-533-graph branch from f4d83bc to 76293bb Compare April 13, 2026 09:55
@jvillafanez
Copy link
Copy Markdown
Member

For GRPC, we can use the context. There is work already done as part of the brute force protection for sharing, and the mechanism can be reused (https://github.com/owncloud/reva/blob/d1e2a4cfd79b7b3a0ed7b26b8de6f1f24443eb17/pkg/auth/manager/publicshares/bruteforceprotection.go#L295)
Basically, you use the GRPC context metadata to include a custom key with a specific prefix.

metakey := ocisgrpcmeta.AutoPropPrefix + "mfa-enabled"
metadata.AppendToOutgoingContext(ctx, metakey, "enabled")

We probably should include some useful information instead of just "enabled", mostly to try to prevent tampering.

All the "standard" ocis and reva GRPC services should be configured already to propagate that metadata automatically, so the information should be available everywhere once the information is set in the metadata.

For HTTP, I think we'll have to check what the tracing is doing because I think we should implement this in a similar way. We'll have to check how effective it is for this particular case though.
The alternative is to include our MFA header on each outgoing request and check it on each incoming request. However, it's easy to miss a HTTP request and that would break the propagation chain.

For the office apps, I think it's easy to include the information in our WOPI context (we can add a new field there), and getting the information back from the access token should be trivial. We just need to propagate the information from there using the techniques from the previous points.

In general, I think tracing is working fine, so we should basically copy the propagation model.

@2403905 2403905 force-pushed the feat/OCISDEV-533-graph branch 2 times, most recently from 60173c7 to 21da7b7 Compare April 13, 2026 13:43
@jvillafanez
Copy link
Copy Markdown
Member

Anything wrong with #12108 (comment) ?
Just setting the mfa value with the prefixed key in the context should be enough to make the mfa value accessible through all the GRPC services. There is already code prepared for that, no need to duplicate the effort. You have a working example in https://github.com/owncloud/reva/blob/260a74454e4a1929312385c398ae55b74417c2e2/pkg/auth/manager/publicshares/bruteforceprotection.go#L295

What is missing is the propagation through HTTP. The steps should be roughly like:

  1. Server middleware checks if the MFA header is present.
  2. If present, it stores the data in the context using the metadata.AppendToOutgoingContext method. The key must use our custom prefix so it's auto-propagated through the GRPC calls.
  3. Once a request happens from the service, it needs to check the metadata metadata.FromIncomingContext, and check for the prefixed keys. Any HTTP call should get those prefixed keys and send the corresponding values using the HTTP headers. Prefixing headers like X-OCIS-AutoProp-* will help to identify which information needs to be propagated.

}

if m.createVaultHome {
// TODO there is no MFA context
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is there no MFA context here? The mfa middleware should add it. Maybe createHome middleware runs BEFORE mfa middleware?

Comment thread services/storage-users/pkg/config/config.go Outdated
Comment thread .gitignore
@2403905 2403905 force-pushed the feat/OCISDEV-533-graph branch 2 times, most recently from 9211b6f to 33c192f Compare April 23, 2026 13:11
2403905 added 5 commits April 23, 2026 23:16
feat: Separate the storage-users and graph to handle

feat: move the space create cache

configurate the vault srorage Postprocessing

hide the assignment of the MountID to VaultStorageProviderID behind a flag to avoid the ID mismatch

configurate the proxy to create the vault home space

update the filter

Make the graph able to handle vault prefix. no extra service needed

apply the vault filter to sharedbyme and sharedwithme

added vault-storage docker

use squashed reva

fix the finishUpload event

restict webdav copy/move from the vault
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a dedicated “vault” storage provider (with optional MFA enforcement) by adding a vault-mode storage-users instance and extending Graph/WebDAV + supporting services to route and filter vault requests appropriately.

Changes:

  • Add a vault storage provider ID and wiring to route /vault/* spaces to a dedicated storage-users-vault service.
  • Enforce/propagate MFA state across HTTP → go-micro → gRPC hops and add gRPC-side MFA blocking for vault storage.
  • Update Graph (vault-mode routing + filtering) and WebDAV copy/move restrictions for vault resources.

Reviewed changes

Copilot reviewed 36 out of 59 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
vendor/modules.txt Bump vendored reva/v2 version.
vendor/github.com/owncloud/reva/v2/pkg/utils/utils.go Add VaultStorageProviderID constant.
vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/upload/upload.go Include StorageId in postprocessing event ResourceID.
vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/options/options.go Add events consumer_group option + default.
vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go Use configurable consumer group + ignore events for other storages.
vendor/github.com/owncloud/reva/v2/pkg/storage/registry/spaces/spaces.go Filter vault spaces unless storage_id is explicitly requested.
vendor/github.com/owncloud/reva/v2/pkg/events/postprocessing.go Add ResourceID to postprocessing events.
vendor/github.com/owncloud/reva/v2/pkg/ctx/mfactx.go Define MFA HTTP header + gRPC autoprop metadata key.
vendor/github.com/owncloud/reva/v2/pkg/auth/manager/serviceaccounts/serviceaccounts.go Import order adjustment (vendored).
vendor/github.com/owncloud/reva/v2/pkg/auth/manager/oidc/oidc.go Import order adjustment (vendored).
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go Simplify storage provider client acquisition.
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocdav/ocdav.go Consider StorageId when checking parent/child refs; add vault destination restriction helper.
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocdav/move.go Block MOVE from vault to non-vault destinations.
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocdav/copy.go Block COPY from vault to non-vault destinations.
vendor/github.com/owncloud/reva/v2/internal/http/services/archiver/handler.go Emit X-Ocis-Mfa-Required header on MFA-related permission denials.
vendor/github.com/owncloud/reva/v2/internal/http/interceptors/auth/auth.go Forward MFA HTTP header into outgoing gRPC metadata.
vendor/github.com/owncloud/reva/v2/internal/grpc/services/storageprovider/storageprovider.go Ensure storage provider ID is set on additional resource IDs in Stat responses.
vendor/github.com/owncloud/reva/v2/internal/grpc/services/gateway/storageprovidercache.go Include storage_id in cache keying for provider listing.
vendor/github.com/owncloud/reva/v2/internal/grpc/services/gateway/storageprovider.go Propagate storage_id through CreateHome/CreateStorageSpace/ListStorageSpaces.
vendor/github.com/owncloud/reva/v2/internal/grpc/interceptors/auth/mfa.go Add gRPC-side MFA blocking response helper.
vendor/github.com/owncloud/reva/v2/internal/grpc/interceptors/auth/auth.go Add mfa_enabled option and enforce MFA via incoming metadata.
services/webdav/pkg/service/v0/service.go Forward MFA status via go-micro metadata for thumbnails calls.
services/thumbnails/pkg/thumbnail/imgsource/cs3.go Bridge go-micro MFA metadata into gRPC metadata for gateway calls.
services/thumbnails/pkg/service/grpc/v0/service.go Bridge MFA metadata during Stat calls used for thumbnail generation.
services/storage-users/pkg/revaconfig/drivers.go Pass mount ID + event consumer group into reva storage driver configs.
services/storage-users/pkg/revaconfig/config.go Enable mfa_enabled in reva auth interceptor when vault mode is active.
services/storage-users/pkg/config/defaults/defaultconfig.go Force mount ID to VaultStorageProviderID in vault mode.
services/storage-users/pkg/config/config.go Add vault-mode toggle + consumer group config fields.
services/proxy/pkg/middleware/options.go Add MFA store + CreateVaultHome option plumbing.
services/proxy/pkg/middleware/mfa.go Persist MFA verification status for non-OIDC flows.
services/proxy/pkg/middleware/create_home.go Optionally provision vault home (forcing MFA metadata for provisioning call).
services/proxy/pkg/config/defaults/defaultconfig.go Route /vault/graph/ to Graph via proxy policies.
services/proxy/pkg/config/config.go Add create_vault_home config flag.
services/proxy/pkg/command/server.go Wire MFA store and CreateVaultHome into middleware chain.
services/postprocessing/pkg/postprocessing/postprocessing.go Include ResourceID in emitted PostprocessingFinished events.
services/policies/pkg/service/event/service.go Propagate ResourceID into PostprocessingStepFinished events.
services/graph/pkg/service/v0/spacetemplates.go Use vault-mode to target the vault storage-users instance for template operations.
services/graph/pkg/service/v0/sharedwithme.go Filter received shares by vault vs non-vault mode.
services/graph/pkg/service/v0/sharedbyme.go Filter “shared by me” results by vault vs non-vault mode.
services/graph/pkg/service/v0/service.go Add /vault/graph routing + require MFA for drives listing endpoints.
services/graph/pkg/service/v0/graph_test.go Update tests to execute via router (middleware-aware).
services/graph/pkg/service/v0/drives.go Force storage_id in vault mode for space creation/listing.
services/graph/pkg/service/v0/driveitems_test.go Add tests ensuring vault mode injects storage_id filter.
services/graph/pkg/service/v0/driveitems.go Inject storage_id filter for root drive children listing in vault mode.
services/graph/pkg/middleware/vault.go Add vault-mode context/middleware.
services/graph/pkg/middleware/mfa.go Add Graph middleware to require MFA.
services/graph/pkg/middleware/auth.go Propagate MFA status into outgoing gRPC metadata.
services/graph/pkg/config/service.go Make Graph service name configurable via env var.
services/graph/pkg/config/config.go Add Graph vault-mode toggle.
services/gateway/pkg/revaconfig/config.go Register dedicated vault storage provider and mountpoints.
services/collaboration/pkg/service/grpc/v0/service.go Carry MFA state into WOPI context.
services/collaboration/pkg/middleware/wopicontext.go Propagate WOPI MFA claim into outgoing gRPC metadata.
services/collaboration/pkg/connector/httpadapter.go Propagate MFA HTTP header into outgoing gRPC metadata for connector ops.
go.mod / go.sum Update dependencies for reva bump.
deployments/examples/ocis_full/vault-storage.yml Add example compose overlay for vault storage-users service.
deployments/examples/ocis_full/.env Add VAULT_STORAGE compose overlay toggle.
.make/go.mk Adjust debug docker build flags.
.gitignore Add ignores for local agent/tool files.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

TLSRootCaCertPath string `yaml:"tls_root_ca_cert_path" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;STORAGE_USERS_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided STORAGE_USERS_EVENTS_TLS_INSECURE will be seen as false." introductionVersion:"pre5.0"`
EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;STORAGE_USERS_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services." introductionVersion:"pre5.0"`
NumConsumers int `yaml:"num_consumers" env:"STORAGE_USERS_EVENTS_NUM_CONSUMERS" desc:"The amount of concurrent event consumers to start. Event consumers are used for post-processing files. Multiple consumers increase parallelisation, but will also increase CPU and memory demands. The setting has no effect when the OCIS_ASYNC_UPLOADS is set to false. The default and minimum value is 1." introductionVersion:"pre5.0"`
ConsumerGroup string `yaml:"consumer_group" env:"STORAGE_USERS_EVENTS_CONSUMER_GROUP" desc:"The consumer group name to use for the event consumers. The consumer group name is used to identify the consumers." introductionVersion:"Deledda"`

Validation Validation `yaml:"validation"`

EnableVaultMode bool `yaml:"enable_vault_mode" env:"GRAPH_ENABLE_VAULT_MODE" desc:"Enable vault mode for the graph service runned in addition to the regular graph service. Required the running the storage-users-vault additional service." introductionVersion:"Deledda"`
Comment thread .make/go.mk
Comment on lines 114 to 123
debug-linux-docker-amd64: release-dirs
GOOS=linux \
GOARCH=amd64 \
go build \
-gcflags="all=-N -l" \
-tags 'netgo $(TAGS)' \
-buildmode=exe \
-trimpath \
-ldflags '-extldflags "-static" $(DEBUG_LDFLAGS) $(DOCKER_LDFLAGS)' \
-o '$(DIST)/binaries/$(EXECUTABLE)-linux-amd64' \
./cmd/$(NAME)
Comment on lines +57 to +62
storageUsersAddress := g.config.Spaces.StorageUsersAddress
if middleware.IsVaultMode(ctx) {
// TODO add the environment variable
storageUsersAddress = storageUsersAddress + "-vault"
}
mdc := metadata.NewCS3(g.config.Reva.Address, storageUsersAddress)
Comment on lines +48 to +49
EnableVaultMode bool `yaml:"enable_vault_mode" env:"STORAGE_USERS_ENABLE_VAULT_MODE" desc:"Enable vault mode for the storage-users service was run in addition to the regular storage-users service by owerrwiting the MountID to VaultStorageProviderID. Required the running the storage-users-vault additional service." introductionVersion:"Deledda"`

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a dedicated “vault” storage area intended to be MFA-protected by splitting responsibilities across a dedicated storage-users-vault instance and adding vault-aware routing/filtering in Graph, WebDAV, and supporting services.

Changes:

  • Add a vault storage provider ID and wire a dedicated storage-users-vault provider/mount (/vault/users, /vault/projects) into the gateway registry and space/provider lookups.
  • Enforce/propagate MFA across HTTP → gRPC hops (proxy, graph, webdav, thumbnails, collaboration/WOPI) and add a gRPC MFA gate for the vault storage-users instance.
  • Add vault-mode Graph routing (/vault/graph/...) and filter spaces/shares/drives based on the vault storage provider.

Reviewed changes

Copilot reviewed 36 out of 59 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
vendor/modules.txt Bumps vendored module listing for updated reva version.
vendor/github.com/owncloud/reva/v2/pkg/utils/utils.go Adds VaultStorageProviderID constant.
vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/upload/upload.go Includes StorageId in emitted postprocessing ResourceID.
vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/options/options.go Adds events consumer_group option with default.
vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go Uses configurable consumer group + ignores postprocessing events for other storages.
vendor/github.com/owncloud/reva/v2/pkg/storage/registry/spaces/spaces.go Adds vault-aware filtering and supports storage_id filter propagation.
vendor/github.com/owncloud/reva/v2/pkg/events/postprocessing.go Extends postprocessing events with ResourceID.
vendor/github.com/owncloud/reva/v2/pkg/ctx/mfactx.go Introduces MFA metadata/header constants for propagation.
vendor/github.com/owncloud/reva/v2/pkg/auth/manager/serviceaccounts/serviceaccounts.go Import ordering adjustment due to vendoring changes.
vendor/github.com/owncloud/reva/v2/pkg/auth/manager/oidc/oidc.go Import ordering adjustment due to vendoring changes.
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go Simplifies storage provider client acquisition.
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocdav/ocdav.go Adds storage ID check in recursion detection + helper to restrict vault copies/moves.
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocdav/move.go Blocks moving from vault to non-vault destinations.
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocdav/copy.go Blocks copying from vault to non-vault destinations.
vendor/github.com/owncloud/reva/v2/internal/http/services/archiver/handler.go Emits MFA-required header + status behavior for MFA-protected accesses.
vendor/github.com/owncloud/reva/v2/internal/http/interceptors/auth/auth.go Forwards MFA status from HTTP header into outgoing gRPC metadata.
vendor/github.com/owncloud/reva/v2/internal/grpc/services/storageprovider/storageprovider.go Ensures storage provider IDs are set (incl. root_info.id).
vendor/github.com/owncloud/reva/v2/internal/grpc/services/gateway/storageprovidercache.go Improves cache key derivation to include storage_id and avoid collisions.
vendor/github.com/owncloud/reva/v2/internal/grpc/services/gateway/storageprovider.go Propagates storage_id through create/list requests for vault handling.
vendor/github.com/owncloud/reva/v2/internal/grpc/interceptors/auth/mfa.go Adds MFA-blocking responder for many storageprovider RPCs.
vendor/github.com/owncloud/reva/v2/internal/grpc/interceptors/auth/auth.go Adds mfa_enabled config and enforces MFA based on incoming metadata.
services/webdav/pkg/service/v0/service.go Bridges MFA into go-micro metadata for thumbnail requests.
services/thumbnails/pkg/thumbnail/imgsource/cs3.go Bridges MFA from go-micro metadata into gRPC outgoing metadata.
services/thumbnails/pkg/service/grpc/v0/service.go Preserves request context and bridges MFA when stat’ing via gateway.
services/storage-users/pkg/revaconfig/drivers.go Passes mount ID and event consumer group into reva driver config.
services/storage-users/pkg/revaconfig/config.go Enables MFA enforcement in auth interceptor when vault mode is on.
services/storage-users/pkg/config/defaults/defaultconfig.go Sets mount ID to VaultStorageProviderID when vault mode is enabled.
services/storage-users/pkg/config/config.go Adds vault mode and event consumer group configuration options.
services/proxy/pkg/middleware/options.go Adds MFAStore and CreateVaultHome middleware options.
services/proxy/pkg/middleware/mfa.go Persists MFA status for non-OIDC requests via store with TTL.
services/proxy/pkg/middleware/create_home.go Optionally provisions a vault home (forcing MFA metadata for provisioning).
services/proxy/pkg/config/defaults/defaultconfig.go Adds proxy policy entry for /vault/graph/.
services/proxy/pkg/config/config.go Adds PROXY_CREATE_VAULT_HOME config option.
services/proxy/pkg/command/server.go Wires MFAStore and CreateVaultHome into middleware chain.
services/postprocessing/pkg/postprocessing/postprocessing.go Includes ResourceID in finished postprocessing event.
services/policies/pkg/service/event/service.go Propagates ResourceID through policies-driven postprocessing events.
services/graph/pkg/service/v0/spacetemplates.go Selects storage-users(-vault) address based on vault mode.
services/graph/pkg/service/v0/sharedwithme.go Filters “shared with me” results based on vault vs regular mode.
services/graph/pkg/service/v0/sharedbyme.go Filters “shared by me” results based on vault vs regular mode.
services/graph/pkg/service/v0/service.go Adds /vault/graph routes and applies vault/MFA middleware.
services/graph/pkg/service/v0/graph_test.go Updates tests to route through the service router for middleware coverage.
services/graph/pkg/service/v0/drives.go Forces vault storage_id in Create/List flows when vault mode is enabled.
services/graph/pkg/service/v0/driveitems_test.go Adds tests for vault-mode storage_id filtering behavior.
services/graph/pkg/service/v0/driveitems.go Forces vault storage_id when listing personal root drive children in vault mode.
services/graph/pkg/middleware/vault.go Adds vault-mode context marker + middleware.
services/graph/pkg/middleware/mfa.go Adds Graph-level RequireMFA middleware.
services/graph/pkg/middleware/auth.go Propagates MFA state to outgoing gRPC metadata from Graph.
services/graph/pkg/config/service.go Makes graph service name configurable via env/yaml.
services/graph/pkg/config/config.go Adds GRAPH_ENABLE_VAULT_MODE config option.
services/gateway/pkg/revaconfig/config.go Registers dedicated storage-users-vault provider with vault mount points.
services/collaboration/pkg/service/grpc/v0/service.go Carries MFA status into WOPI token context.
services/collaboration/pkg/middleware/wopicontext.go Propagates MFA status from WOPI token into outgoing gRPC metadata.
services/collaboration/pkg/connector/httpadapter.go Propagates MFA header into outgoing gRPC metadata for connector calls.
go.sum Updates checksums for new reva version.
go.mod Bumps github.com/owncloud/reva/v2 dependency.
deployments/examples/ocis_full/vault-storage.yml Adds example compose overlay to run storage-users-vault and enable vault mode.
deployments/examples/ocis_full/.env Adds VAULT_STORAGE compose overlay toggle.
.make/go.mk Adjusts debug docker build flags (removes -trimpath).
.gitignore Adds additional ignore entries (but introduces duplication).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +57 to +62
storageUsersAddress := g.config.Spaces.StorageUsersAddress
if middleware.IsVaultMode(ctx) {
// TODO add the environment variable
storageUsersAddress = storageUsersAddress + "-vault"
}
mdc := metadata.NewCS3(g.config.Reva.Address, storageUsersAddress)

m.Route(options.Config.HTTP.Root, graphRoutes)

// Ini the Vault routes
Comment on lines +404 to +406
m.Route("/vault/graph", func(r chi.Router) {
r.Use(requireMFA)
r.Use(graphm.VaultModeMiddleware())
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.

5 participants