Add 'proxy/' from commit '201b9a652685cdfb72ba81c7e7b00ba1c60a0e35'

git-subtree-dir: proxy
git-subtree-mainline: 571d96e856
git-subtree-split: 201b9a6526
This commit is contained in:
A.Unger
2020-09-18 12:47:26 +02:00
107 changed files with 8151 additions and 0 deletions

11
proxy/.codacy.yml Normal file
View File

@@ -0,0 +1,11 @@
---
exclude_paths:
- CHANGELOG.md
- changelog/**
- docs/**
- config/**
- pkg/proto/**
- '**_test.go'
- .codacy.yml
...

2
proxy/.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
*
!bin/

698
proxy/.drone.star Normal file
View File

@@ -0,0 +1,698 @@
def main(ctx):
before = [
testing(ctx),
]
stages = [
docker(ctx, 'amd64'),
docker(ctx, 'arm64'),
docker(ctx, 'arm'),
binary(ctx, 'linux'),
binary(ctx, 'darwin'),
binary(ctx, 'windows'),
]
after = [
manifest(ctx),
changelog(ctx),
readme(ctx),
badges(ctx),
website(ctx),
]
return before + stages + after
def testing(ctx):
return {
'kind': 'pipeline',
'type': 'docker',
'name': 'testing',
'platform': {
'os': 'linux',
'arch': 'amd64',
},
'steps': [
{
'name': 'generate',
'image': 'webhippie/golang:1.13',
'pull': 'always',
'commands': [
'make generate',
],
'volumes': [
{
'name': 'gopath',
'path': '/srv/app',
},
],
},
{
'name': 'vet',
'image': 'webhippie/golang:1.13',
'pull': 'always',
'commands': [
'make vet',
],
'volumes': [
{
'name': 'gopath',
'path': '/srv/app',
},
],
},
{
'name': 'staticcheck',
'image': 'golangci/golangci-lint:latest',
'pull': 'always',
'commands': [
'golangci-lint run',
],
'volumes': [
{
'name': 'gopath',
'path': '/srv/app',
},
],
},
{
'name': 'lint',
'image': 'webhippie/golang:1.13',
'pull': 'always',
'commands': [
'make lint',
],
'volumes': [
{
'name': 'gopath',
'path': '/srv/app',
},
],
},
{
'name': 'build',
'image': 'webhippie/golang:1.13',
'pull': 'always',
'commands': [
'make build',
],
'volumes': [
{
'name': 'gopath',
'path': '/srv/app',
},
],
},
{
'name': 'test',
'image': 'webhippie/golang:1.13',
'pull': 'always',
'commands': [
'make test',
],
'volumes': [
{
'name': 'gopath',
'path': '/srv/app',
},
],
},
{
'name': 'codacy',
'image': 'plugins/codacy:1',
'pull': 'always',
'settings': {
'token': {
'from_secret': 'codacy_token',
},
},
},
],
'volumes': [
{
'name': 'gopath',
'temp': {},
},
],
'trigger': {
'ref': [
'refs/heads/master',
'refs/tags/**',
'refs/pull/**',
],
},
}
def docker(ctx, arch):
return {
'kind': 'pipeline',
'type': 'docker',
'name': arch,
'platform': {
'os': 'linux',
'arch': arch,
},
'steps': [
{
'name': 'generate',
'image': 'webhippie/golang:1.13',
'pull': 'always',
'commands': [
'make generate',
],
'volumes': [
{
'name': 'gopath',
'path': '/srv/app',
},
],
},
{
'name': 'build',
'image': 'webhippie/golang:1.13',
'pull': 'always',
'commands': [
'make build',
],
'volumes': [
{
'name': 'gopath',
'path': '/srv/app',
},
],
},
{
'name': 'dryrun',
'image': 'plugins/docker:18.09',
'pull': 'always',
'settings': {
'dry_run': True,
'tags': 'linux-%s' % (arch),
'dockerfile': 'docker/Dockerfile.linux.%s' % (arch),
'repo': ctx.repo.slug,
},
'when': {
'ref': {
'include': [
'refs/pull/**',
],
},
},
},
{
'name': 'docker',
'image': 'plugins/docker:18.09',
'pull': 'always',
'settings': {
'username': {
'from_secret': 'docker_username',
},
'password': {
'from_secret': 'docker_password',
},
'auto_tag': True,
'auto_tag_suffix': 'linux-%s' % (arch),
'dockerfile': 'docker/Dockerfile.linux.%s' % (arch),
'repo': ctx.repo.slug,
},
'when': {
'ref': {
'exclude': [
'refs/pull/**',
],
},
},
},
],
'volumes': [
{
'name': 'gopath',
'temp': {},
},
],
'depends_on': [
'testing',
],
'trigger': {
'ref': [
'refs/heads/master',
'refs/tags/**',
'refs/pull/**',
],
},
}
def binary(ctx, name):
if ctx.build.event == "tag":
settings = {
'endpoint': {
'from_secret': 's3_endpoint',
},
'access_key': {
'from_secret': 'aws_access_key_id',
},
'secret_key': {
'from_secret': 'aws_secret_access_key',
},
'bucket': {
'from_secret': 's3_bucket',
},
'path_style': True,
'strip_prefix': 'dist/release/',
'source': 'dist/release/*',
'target': '/ocis/%s/%s' % (ctx.repo.name.replace("ocis-", ""), ctx.build.ref.replace("refs/tags/v", "")),
}
else:
settings = {
'endpoint': {
'from_secret': 's3_endpoint',
},
'access_key': {
'from_secret': 'aws_access_key_id',
},
'secret_key': {
'from_secret': 'aws_secret_access_key',
},
'bucket': {
'from_secret': 's3_bucket',
},
'path_style': True,
'strip_prefix': 'dist/release/',
'source': 'dist/release/*',
'target': '/ocis/%s/testing' % (ctx.repo.name.replace("ocis-", "")),
}
return {
'kind': 'pipeline',
'type': 'docker',
'name': name,
'platform': {
'os': 'linux',
'arch': 'amd64',
},
'steps': [
{
'name': 'generate',
'image': 'webhippie/golang:1.13',
'pull': 'always',
'commands': [
'make generate',
],
'volumes': [
{
'name': 'gopath',
'path': '/srv/app',
},
],
},
{
'name': 'build',
'image': 'webhippie/golang:1.13',
'pull': 'always',
'commands': [
'make release-%s' % (name),
],
'volumes': [
{
'name': 'gopath',
'path': '/srv/app',
},
],
},
{
'name': 'finish',
'image': 'webhippie/golang:1.13',
'pull': 'always',
'commands': [
'make release-finish',
],
'volumes': [
{
'name': 'gopath',
'path': '/srv/app',
},
],
},
{
'name': 'upload',
'image': 'plugins/s3:1',
'pull': 'always',
'settings': settings,
'when': {
'ref': [
'refs/heads/master',
'refs/tags/**',
],
},
},
{
'name': 'changelog',
'image': 'toolhippie/calens:latest',
'pull': 'always',
'commands': [
'calens --version %s -o dist/CHANGELOG.md' % ctx.build.ref.replace("refs/tags/v", "").split("-")[0],
],
'when': {
'ref': [
'refs/tags/**',
],
},
},
{
'name': 'release',
'image': 'plugins/github-release:1',
'pull': 'always',
'settings': {
'api_key': {
'from_secret': 'github_token',
},
'files': [
'dist/release/*',
],
'title': ctx.build.ref.replace("refs/tags/v", ""),
'note': 'dist/CHANGELOG.md',
'overwrite': True,
'prerelease': len(ctx.build.ref.split("-")) > 1,
},
'when': {
'ref': [
'refs/tags/**',
],
},
},
],
'volumes': [
{
'name': 'gopath',
'temp': {},
},
],
'depends_on': [
'testing',
],
'trigger': {
'ref': [
'refs/heads/master',
'refs/tags/**',
'refs/pull/**',
],
},
}
def manifest(ctx):
return {
'kind': 'pipeline',
'type': 'docker',
'name': 'manifest',
'platform': {
'os': 'linux',
'arch': 'amd64',
},
'steps': [
{
'name': 'execute',
'image': 'plugins/manifest:1',
'pull': 'always',
'settings': {
'username': {
'from_secret': 'docker_username',
},
'password': {
'from_secret': 'docker_password',
},
'spec': 'docker/manifest.tmpl',
'auto_tag': True,
'ignore_missing': True,
},
},
],
'depends_on': [
'amd64',
'arm64',
'arm',
'linux',
'darwin',
'windows',
],
'trigger': {
'ref': [
'refs/heads/master',
'refs/tags/**',
],
},
}
def changelog(ctx):
repo_slug = ctx.build.source_repo if ctx.build.source_repo else ctx.repo.slug
return {
'kind': 'pipeline',
'type': 'docker',
'name': 'changelog',
'platform': {
'os': 'linux',
'arch': 'amd64',
},
'clone': {
'disable': True,
},
'steps': [
{
'name': 'clone',
'image': 'plugins/git-action:1',
'pull': 'always',
'settings': {
'actions': [
'clone',
],
'remote': 'https://github.com/%s' % (repo_slug),
'branch': ctx.build.source if ctx.build.event == 'pull_request' else 'master',
'path': '/drone/src',
'netrc_machine': 'github.com',
'netrc_username': {
'from_secret': 'github_username',
},
'netrc_password': {
'from_secret': 'github_token',
},
},
},
{
'name': 'generate',
'image': 'webhippie/golang:1.13',
'pull': 'always',
'commands': [
'make changelog',
],
},
{
'name': 'diff',
'image': 'owncloud/alpine:latest',
'pull': 'always',
'commands': [
'git diff',
],
},
{
'name': 'output',
'image': 'owncloud/alpine:latest',
'pull': 'always',
'commands': [
'cat CHANGELOG.md',
],
},
{
'name': 'publish',
'image': 'plugins/git-action:1',
'pull': 'always',
'settings': {
'actions': [
'commit',
'push',
],
'message': 'Automated changelog update [skip ci]',
'branch': 'master',
'author_email': 'devops@owncloud.com',
'author_name': 'ownClouders',
'netrc_machine': 'github.com',
'netrc_username': {
'from_secret': 'github_username',
},
'netrc_password': {
'from_secret': 'github_token',
},
},
'when': {
'ref': {
'exclude': [
'refs/pull/**',
],
},
},
},
],
'depends_on': [
'manifest',
],
'trigger': {
'ref': [
'refs/heads/master',
'refs/pull/**',
],
},
}
def readme(ctx):
return {
'kind': 'pipeline',
'type': 'docker',
'name': 'readme',
'platform': {
'os': 'linux',
'arch': 'amd64',
},
'steps': [
{
'name': 'execute',
'image': 'sheogorath/readme-to-dockerhub:latest',
'pull': 'always',
'environment': {
'DOCKERHUB_USERNAME': {
'from_secret': 'docker_username',
},
'DOCKERHUB_PASSWORD': {
'from_secret': 'docker_password',
},
'DOCKERHUB_REPO_PREFIX': ctx.repo.namespace,
'DOCKERHUB_REPO_NAME': ctx.repo.name,
'SHORT_DESCRIPTION': 'Docker images for %s' % (ctx.repo.name),
'README_PATH': 'README.md',
},
},
],
'depends_on': [
'changelog',
],
'trigger': {
'ref': [
'refs/heads/master',
'refs/tags/**',
],
},
}
def badges(ctx):
return {
'kind': 'pipeline',
'type': 'docker',
'name': 'badges',
'platform': {
'os': 'linux',
'arch': 'amd64',
},
'steps': [
{
'name': 'execute',
'image': 'plugins/webhook:1',
'pull': 'always',
'settings': {
'urls': {
'from_secret': 'microbadger_url',
},
},
},
],
'depends_on': [
'readme',
],
'trigger': {
'ref': [
'refs/heads/master',
'refs/tags/**',
],
},
}
def website(ctx):
return {
'kind': 'pipeline',
'type': 'docker',
'name': 'website',
'platform': {
'os': 'linux',
'arch': 'amd64',
},
'steps': [
{
'name': 'prepare',
'image': 'owncloudci/alpine:latest',
'commands': [
'make docs-copy'
],
},
{
'name': 'test',
'image': 'webhippie/hugo:latest',
'commands': [
'cd hugo',
'hugo',
],
},
{
'name': 'list',
'image': 'owncloudci/alpine:latest',
'commands': [
'tree hugo/public',
],
},
{
'name': 'publish',
'image': 'plugins/gh-pages:1',
'pull': 'always',
'settings': {
'username': {
'from_secret': 'github_username',
},
'password': {
'from_secret': 'github_token',
},
'pages_directory': 'docs/',
'target_branch': 'docs',
},
'when': {
'ref': {
'exclude': [
'refs/pull/**',
],
},
},
},
{
'name': 'downstream',
'image': 'plugins/downstream',
'settings': {
'server': 'https://cloud.drone.io/',
'token': {
'from_secret': 'drone_token',
},
'repositories': [
'owncloud/owncloud.github.io@source',
],
},
'when': {
'ref': {
'exclude': [
'refs/pull/**',
],
},
},
},
],
'depends_on': [
'badges',
],
'trigger': {
'ref': [
'refs/heads/master',
'refs/pull/**',
],
},
}

35
proxy/.editorconfig Normal file
View File

@@ -0,0 +1,35 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
[Makefile]
indent_style = tab
indent_size = 4
[*.go]
indent_style = tab
indent_size = 4
[*.starlark]
indent_style = space
indent_size = 2
[*.{yml,json}]
indent_style = space
indent_size = 2
[*.{js,vue}]
indent_style = space
indent_size = 2
[*.{css,less}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = true

12
proxy/.github/config.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# Configuration for update-docs - https://github.com/behaviorbot/update-docs
# Comment to be posted to on PRs that don't update documentation
updateDocsComment: >
Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would create a [changelog](https://github.com/owncloud/ocis-proxy/blob/master/changelog/README.md) item based on your changes.
updateDocsWhiteList:
- Tests-only
- tests-only
- Tests-Only
updateDocsTargetFiles:
- changelog/unreleased/

0
proxy/.github/issue_template.md vendored Normal file
View File

View File

98
proxy/.github/settings.yml vendored Normal file
View File

@@ -0,0 +1,98 @@
---
repository:
name: ocis-proxy
description: ':bridge_at_night: Reverse proxy for oCIS'
homepage: https://owncloud.github.io/ocis-proxy/
topics: ocis
private: false
has_issues: true
has_projects: false
has_wiki: false
has_downloads: false
default_branch: master
allow_squash_merge: true
allow_merge_commit: true
allow_rebase_merge: true
labels:
- name: bug
color: d73a4a
description: Something isn't working
- name: documentation
color: 0075ca
description: Improvements or additions to documentation
- name: duplicate
color: cfd3d7
description: This issue or pull request already exists
- name: enhancement
color: a2eeef
description: New feature or request
- name: good first issue
color: 7057ff
description: Good for newcomers
- name: help wanted
color: 008672
description: Extra attention is needed
- name: invalid
color: e4e669
description: This doesn't seem right
- name: question
color: d876e3
description: Further information is requested
- name: wontfix
color: ffffff
description: This will not be worked on
- name: effort/trivial
color: c2e0c6
description: Required effort to finish task
- name: effort/0.25d
color: c2e0c6
description: Required effort to finish task
- name: effort/0.5d
color: c2e0c6
description: Required effort to finish task
- name: effort/1d
color: c2e0c6
description: Required effort to finish task
- name: effort/2d
color: c2e0c6
description: Required effort to finish task
- name: effort/4d
color: c2e0c6
description: Required effort to finish task
- name: effort/5d
color: c2e0c6
description: Required effort to finish task
- name: effort/10d
color: c2e0c6
description: Required effort to finish task
teams:
- name: ci
permission: admin
- name: employees
permission: push
branches:
- name: master
protection:
required_pull_request_reviews:
required_approving_review_count: 1
dismiss_stale_reviews: false
require_code_owner_reviews: false
dismissal_restrictions: {}
required_status_checks:
strict: true
contexts:
- continuous-integration/drone/pr
enforce_admins: false
restrictions:
users: []
teams:
- ci
- employees
...

5
proxy/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
coverage.out
/bin
/dist
/hugo

33
proxy/.golangci.yaml Normal file
View File

@@ -0,0 +1,33 @@
issues:
exclude-rules:
- path: pkg/middleware/account_uuid.go
text: "SA4006:"
linters:
- staticcheck
- path: pkg/proxy/proxy_integration_test.go
linters:
- bodyclose
linters:
enable:
- bodyclose
- deadcode
- gosimple
- govet
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
- depguard
- golint
- unconvert
# - scopelint
- maligned
- misspell
- prealloc
#- gosec
disable:
- errcheck
- ineffassign
- scopelint

478
proxy/CHANGELOG.md Normal file
View File

@@ -0,0 +1,478 @@
# Changelog for [unreleased] (UNRELEASED)
The following sections list the changes for ocis-proxy unreleased.
[unreleased]: https://github.com/owncloud/ocis-proxy/compare/v0.7.0...master
## Summary
* Bugfix - Fix director selection: [#99](https://github.com/owncloud/ocis-proxy/pull/99)
* Bugfix - Add settings API and app endpoints to example config: [#93](https://github.com/owncloud/ocis-proxy/pull/93)
* Change - Remove accounts caching: [#100](https://github.com/owncloud/ocis-proxy/pull/100)
* Enhancement - Add hello API and app endpoints to example config and builtin config: [#96](https://github.com/owncloud/ocis-proxy/pull/96)
* Enhancement - Add roleIDs to the access token: [#95](https://github.com/owncloud/ocis-proxy/pull/95)
## Details
* Bugfix - Fix director selection: [#99](https://github.com/owncloud/ocis-proxy/pull/99)
We fixed a bug where simultaneous requests could be executed on the wrong backend.
https://github.com/owncloud/ocis-proxy/pull/99
* Bugfix - Add settings API and app endpoints to example config: [#93](https://github.com/owncloud/ocis-proxy/pull/93)
We had the ocis-settings API and app endpoints in the builtin config already, but they were
missing in the example config. Added them for consistency.
https://github.com/owncloud/ocis-proxy/pull/93
* Change - Remove accounts caching: [#100](https://github.com/owncloud/ocis-proxy/pull/100)
We removed the accounts cache in order to avoid problems with accounts that have been updated in
the accounts service.
https://github.com/owncloud/ocis-proxy/pull/100
* Enhancement - Add hello API and app endpoints to example config and builtin config: [#96](https://github.com/owncloud/ocis-proxy/pull/96)
We added the ocis-hello API and app endpoints to both the example config and the builtin config.
https://github.com/owncloud/ocis-proxy/pull/96
* Enhancement - Add roleIDs to the access token: [#95](https://github.com/owncloud/ocis-proxy/pull/95)
We are using the roleIDs of the authenticated user for permission checks against
ocis-settings. We added the roleIDs to the access token to have them available quickly.
https://github.com/owncloud/ocis-proxy/pull/95
# Changelog for [0.7.0] (2020-08-21)
The following sections list the changes for ocis-proxy 0.7.0.
[0.7.0]: https://github.com/owncloud/ocis-proxy/compare/v0.6.0...v0.7.0
## Summary
* Enhancement - Add numeric uid and gid to the access token: [#89](https://github.com/owncloud/ocis-proxy/pull/89)
* Enhancement - Add configuration options for the pre-signed url middleware: [#91](https://github.com/owncloud/ocis-proxy/issues/91)
## Details
* Enhancement - Add numeric uid and gid to the access token: [#89](https://github.com/owncloud/ocis-proxy/pull/89)
The eos storage driver is fetching the uid and gid of a user from the access token. This PR is using
the response of the accounts service to mint them in the token.
https://github.com/owncloud/ocis-proxy/pull/89
* Enhancement - Add configuration options for the pre-signed url middleware: [#91](https://github.com/owncloud/ocis-proxy/issues/91)
Added an option to define allowed http methods for pre-signed url requests. This is useful
since we only want clients to GET resources and don't upload anything with presigned requests.
https://github.com/owncloud/ocis-proxy/issues/91
https://github.com/owncloud/product/issues/150
# Changelog for [0.6.0] (2020-08-17)
The following sections list the changes for ocis-proxy 0.6.0.
[0.6.0]: https://github.com/owncloud/ocis-proxy/compare/v0.5.0...v0.6.0
## Summary
* Bugfix - Enable new accounts by default: [#79](https://github.com/owncloud/ocis-proxy/pull/79)
* Bugfix - Lookup user by id for presigned URLs: [#85](https://github.com/owncloud/ocis-proxy/pull/85)
* Bugfix - Build docker images with alpine:latest instead of alpine:edge: [#78](https://github.com/owncloud/ocis-proxy/pull/78)
* Change - Add settings and ocs group routes: [#81](https://github.com/owncloud/ocis-proxy/pull/81)
* Change - Add route for user provisioning API in ocis-ocs: [#80](https://github.com/owncloud/ocis-proxy/pull/80)
## Details
* Bugfix - Enable new accounts by default: [#79](https://github.com/owncloud/ocis-proxy/pull/79)
When new accounts are created, they also need to be enabled to be useable.
https://github.com/owncloud/ocis-proxy/pull/79
* Bugfix - Lookup user by id for presigned URLs: [#85](https://github.com/owncloud/ocis-proxy/pull/85)
Phoenix will send the `userid`, not the `username` as the `OC-Credential` for presigned URLs.
This PR uses the new `ocisid` claim in the OIDC userinfo to pass the userid to the account
middleware.
https://github.com/owncloud/ocis/issues/436
https://github.com/owncloud/ocis-proxy/pull/85
https://github.com/owncloud/ocis-pkg/pull/50
* Bugfix - Build docker images with alpine:latest instead of alpine:edge: [#78](https://github.com/owncloud/ocis-proxy/pull/78)
ARM builds were failing when built on alpine:edge, so we switched to alpine:latest instead.
https://github.com/owncloud/ocis-proxy/pull/78
* Change - Add settings and ocs group routes: [#81](https://github.com/owncloud/ocis-proxy/pull/81)
Route settings requests and ocs group related requests to new services
https://github.com/owncloud/ocis-proxy/pull/81
* Change - Add route for user provisioning API in ocis-ocs: [#80](https://github.com/owncloud/ocis-proxy/pull/80)
We added a route to send requests on the user provisioning API endpoints to ocis-ocs.
https://github.com/owncloud/ocis-proxy/pull/80
# Changelog for [0.5.0] (2020-07-23)
The following sections list the changes for ocis-proxy 0.5.0.
[0.5.0]: https://github.com/owncloud/ocis-proxy/compare/v0.4.0...v0.5.0
## Summary
* Bugfix - Provide token configuration from config: [#69](https://github.com/owncloud/ocis-proxy/pull/69)
* Bugfix - Provide token configuration from config: [#76](https://github.com/owncloud/ocis-proxy/pull/76)
* Change - Add OIDC config flags: [#66](https://github.com/owncloud/ocis-proxy/pull/66)
* Change - Mint new username property in the reva token: [#62](https://github.com/owncloud/ocis-proxy/pull/62)
* Enhancement - Add Accounts UI routes: [#65](https://github.com/owncloud/ocis-proxy/pull/65)
* Enhancement - Add option to disable TLS: [#71](https://github.com/owncloud/ocis-proxy/issues/71)
* Enhancement - Only send create home request if an account has been migrated: [#52](https://github.com/owncloud/ocis-proxy/issues/52)
* Enhancement - Create a root span on proxy that propagates down to consumers: [#64](https://github.com/owncloud/ocis-proxy/pull/64)
* Enhancement - Support signed URLs: [#73](https://github.com/owncloud/ocis-proxy/issues/73)
## Details
* Bugfix - Provide token configuration from config: [#69](https://github.com/owncloud/ocis-proxy/pull/69)
Fixed a bug that causes the createHome middleware to crash if no configuration for the
TokenManager is propagated.
https://github.com/owncloud/ocis-proxy/pull/69
* Bugfix - Provide token configuration from config: [#76](https://github.com/owncloud/ocis-proxy/pull/76)
Fixed a bug that causes the createHome middleware to crash if the createHome response has no
Status set
https://github.com/owncloud/ocis-proxy/pull/76
* Change - Add OIDC config flags: [#66](https://github.com/owncloud/ocis-proxy/pull/66)
To authenticate requests with an oidc provider we added two environment variables: -
`PROXY_OIDC_ISSUER="https://localhost:9200"` and - `PROXY_OIDC_INSECURE=true`
This changes ocis-proxy to now load the oidc-middleware by default, requiring a bearer token
and exchanging the email in the OIDC claims for an account id at the ocis-accounts service.
Setting `PROXY_OIDC_ISSUER=""` will disable the OIDC middleware.
https://github.com/owncloud/ocis-proxy/pull/66
* Change - Mint new username property in the reva token: [#62](https://github.com/owncloud/ocis-proxy/pull/62)
An accounts username is now taken from the on_premises_sam_account_name property instead of
the preferred_name. Furthermore the group name (also from on_premises_sam_account_name
property) is now minted into the token as well.
https://github.com/owncloud/ocis-proxy/pull/62
* Enhancement - Add Accounts UI routes: [#65](https://github.com/owncloud/ocis-proxy/pull/65)
The accounts service has a ui that requires routing - `/api/v0/accounts` and - `/accounts.js`
To http://localhost:9181
https://github.com/owncloud/ocis-proxy/pull/65
* Enhancement - Add option to disable TLS: [#71](https://github.com/owncloud/ocis-proxy/issues/71)
Can be used to disable TLS when the ocis-proxy is behind an TLS-Terminating reverse proxy.
Env PROXY_TLS=false or --tls=false
https://github.com/owncloud/ocis-proxy/issues/71
https://github.com/owncloud/ocis-proxy/pull/72
* Enhancement - Only send create home request if an account has been migrated: [#52](https://github.com/owncloud/ocis-proxy/issues/52)
This change adds a check if an account has been migrated by getting it from the ocis-accounts
service. If no account is returned it means it hasn't been migrated.
https://github.com/owncloud/ocis-proxy/issues/52
https://github.com/owncloud/ocis-proxy/pull/63
* Enhancement - Create a root span on proxy that propagates down to consumers: [#64](https://github.com/owncloud/ocis-proxy/pull/64)
In order to propagate and correctly associate a span with a request we need a root span that gets
sent to other services.
https://github.com/owncloud/ocis-proxy/pull/64
* Enhancement - Support signed URLs: [#73](https://github.com/owncloud/ocis-proxy/issues/73)
We added a middleware that verifies signed urls as generated by the owncloud-sdk. This allows
directly downloading large files with browsers instead of using `blob://` urls, which eats
memory ...
https://github.com/owncloud/ocis-proxy/issues/73
https://github.com/owncloud/ocis-proxy/pull/75
https://github.com/owncloud/ocis-ocs/pull/18
https://github.com/owncloud/owncloud-sdk/pull/504
# Changelog for [0.4.0] (2020-06-25)
The following sections list the changes for ocis-proxy 0.4.0.
[0.4.0]: https://github.com/owncloud/ocis-proxy/compare/v0.3.1...v0.4.0
## Summary
* Bugfix - Accounts service response was ignored: [#43](https://github.com/owncloud/ocis-proxy/pull/43)
* Bugfix - Fix x-access-token in header: [#41](https://github.com/owncloud/ocis-proxy/pull/41)
* Change - Point /data endpoint to reva frontend: [#45](https://github.com/owncloud/ocis-proxy/pull/45)
* Change - Send autocreate home request to reva gateway: [#51](https://github.com/owncloud/ocis-proxy/pull/51)
* Change - Update to new accounts API: [#39](https://github.com/owncloud/ocis-proxy/issues/39)
* Enhancement - Retrieve Account UUID From User Claims: [#36](https://github.com/owncloud/ocis-proxy/pull/36)
* Enhancement - Create account if it doesn't exist in ocis-accounts: [#55](https://github.com/owncloud/ocis-proxy/issues/55)
* Enhancement - Disable keep-alive on server-side OIDC requests: [#268](https://github.com/owncloud/ocis/issues/268)
* Enhancement - Make jwt secret configurable: [#41](https://github.com/owncloud/ocis-proxy/pull/41)
* Enhancement - Respect account_enabled flag: [#53](https://github.com/owncloud/ocis-proxy/issues/53)
## Details
* Bugfix - Accounts service response was ignored: [#43](https://github.com/owncloud/ocis-proxy/pull/43)
We fixed an error in the AccountUUID middleware that was responsible for ignoring an account
uuid provided by the accounts service.
https://github.com/owncloud/ocis-proxy/pull/43
* Bugfix - Fix x-access-token in header: [#41](https://github.com/owncloud/ocis-proxy/pull/41)
We fixed setting the x-access-token in the request header, which was broken before.
https://github.com/owncloud/ocis-proxy/pull/41
https://github.com/owncloud/ocis-proxy/pull/46
* Change - Point /data endpoint to reva frontend: [#45](https://github.com/owncloud/ocis-proxy/pull/45)
Adjusted example config files to point /data to the reva frontend.
https://github.com/owncloud/ocis-proxy/pull/45
* Change - Send autocreate home request to reva gateway: [#51](https://github.com/owncloud/ocis-proxy/pull/51)
Send autocreate home request to reva gateway
https://github.com/owncloud/ocis-proxy/pull/51
* Change - Update to new accounts API: [#39](https://github.com/owncloud/ocis-proxy/issues/39)
Update to new accounts API
https://github.com/owncloud/ocis-proxy/issues/39
* Enhancement - Retrieve Account UUID From User Claims: [#36](https://github.com/owncloud/ocis-proxy/pull/36)
OIDC Middleware can make use of uuidFromClaims to trade claims.Email for an account's UUID.
For this, a general purpose cache was added that caches on a per-request basis, meaning
whenever the request parameters match a set of keys, the cached value is returned, saving a
round trip to the accounts service that otherwise would happen in every single request.
https://github.com/owncloud/ocis-proxy/pull/36
* Enhancement - Create account if it doesn't exist in ocis-accounts: [#55](https://github.com/owncloud/ocis-proxy/issues/55)
The accounts_uuid middleware tries to get the account from ocis-accounts. If it doens't exist
there yet the proxy creates the account using the ocis-account api.
https://github.com/owncloud/ocis-proxy/issues/55
https://github.com/owncloud/ocis-proxy/issues/58
* Enhancement - Disable keep-alive on server-side OIDC requests: [#268](https://github.com/owncloud/ocis/issues/268)
This should reduce file-descriptor counts
https://github.com/owncloud/ocis/issues/268
https://github.com/owncloud/ocis-proxy/pull/42
https://github.com/cs3org/reva/pull/787
* Enhancement - Make jwt secret configurable: [#41](https://github.com/owncloud/ocis-proxy/pull/41)
We added a config option for the reva token manager JWTSecret. It was hardcoded before and is now
configurable.
https://github.com/owncloud/ocis-proxy/pull/41
* Enhancement - Respect account_enabled flag: [#53](https://github.com/owncloud/ocis-proxy/issues/53)
If the account returned by the accounts service has the account_enabled flag set to false, the
proxy will return immediately with the status code unauthorized.
https://github.com/owncloud/ocis-proxy/issues/53
# Changelog for [0.3.1] (2020-03-31)
The following sections list the changes for ocis-proxy 0.3.1.
[0.3.1]: https://github.com/owncloud/ocis-proxy/compare/v0.3.0...v0.3.1
## Summary
* Change - Update ocis-pkg: [#30](https://github.com/owncloud/ocis-proxy/pull/30)
## Details
* Change - Update ocis-pkg: [#30](https://github.com/owncloud/ocis-proxy/pull/30)
We updated ocis-pkg from 2.0.2 to 2.2.0.
https://github.com/owncloud/ocis-proxy/pull/30
# Changelog for [0.3.0] (2020-03-30)
The following sections list the changes for ocis-proxy 0.3.0.
[0.3.0]: https://github.com/owncloud/ocis-proxy/compare/v0.2.1...v0.3.0
## Summary
* Change - Insecure http-requests are now redirected to https: [#29](https://github.com/owncloud/ocis-proxy/pull/29)
* Enhancement - Configurable OpenID Connect client: [#27](https://github.com/owncloud/ocis-proxy/pull/27)
* Enhancement - Add policy selectors: [#4](https://github.com/owncloud/ocis-proxy/issues/4)
## Details
* Change - Insecure http-requests are now redirected to https: [#29](https://github.com/owncloud/ocis-proxy/pull/29)
https://github.com/owncloud/ocis-proxy/pull/29
* Enhancement - Configurable OpenID Connect client: [#27](https://github.com/owncloud/ocis-proxy/pull/27)
The proxy will try to authenticate every request with the configured OIDC provider.
See configs/proxy-example.oidc.json for an example-configuration.
https://github.com/owncloud/ocis-proxy/pull/27
* Enhancement - Add policy selectors: [#4](https://github.com/owncloud/ocis-proxy/issues/4)
"Static-Policy" can be configured to always select a specific policy. See:
config/proxy-example.json.
"Migration-Policy" selects policy depending on existence of the uid in the ocis-accounts
service. See: config/proxy-example-migration.json
https://github.com/owncloud/ocis-proxy/issues/4
# Changelog for [0.2.1] (2020-03-25)
The following sections list the changes for ocis-proxy 0.2.1.
[0.2.1]: https://github.com/owncloud/ocis-proxy/compare/v0.2.0...v0.2.1
## Summary
* Bugfix - Set TLS-Certificate correctly: [#25](https://github.com/owncloud/ocis-proxy/pull/25)
## Details
* Bugfix - Set TLS-Certificate correctly: [#25](https://github.com/owncloud/ocis-proxy/pull/25)
https://github.com/owncloud/ocis-proxy/pull/25
# Changelog for [0.2.0] (2020-03-25)
The following sections list the changes for ocis-proxy 0.2.0.
[0.2.0]: https://github.com/owncloud/ocis-proxy/compare/v0.1.0...v0.2.0
## Summary
* Change - Route requests based on regex or query parameters: [#21](https://github.com/owncloud/ocis-proxy/issues/21)
* Enhancement - Proxy client urls in default configuration: [#19](https://github.com/owncloud/ocis-proxy/issues/19)
* Enhancement - Make TLS-Cert configurable: [#14](https://github.com/owncloud/ocis-proxy/pull/14)
## Details
* Change - Route requests based on regex or query parameters: [#21](https://github.com/owncloud/ocis-proxy/issues/21)
Some requests needed to be distinguished based on a pattern or a query parameter. We've
implemented the functionality to route requests based on different conditions.
https://github.com/owncloud/ocis-proxy/issues/21
* Enhancement - Proxy client urls in default configuration: [#19](https://github.com/owncloud/ocis-proxy/issues/19)
Proxy /status.php and index.php/*
https://github.com/owncloud/ocis-proxy/issues/19
* Enhancement - Make TLS-Cert configurable: [#14](https://github.com/owncloud/ocis-proxy/pull/14)
Before a generates certificates on every start was used for dev purposes.
https://github.com/owncloud/ocis-proxy/pull/14
# Changelog for [0.1.0] (2020-03-18)
The following sections list the changes for ocis-proxy 0.1.0.
[0.1.0]: https://github.com/owncloud/ocis-proxy/compare/500e303cb544ed93d84153f01219d77eeee44929...v0.1.0
## Summary
* Change - Initial release of basic version: [#1](https://github.com/owncloud/ocis-proxy/issues/1)
* Enhancement - Load Proxy Policies at Runtime: [#17](https://github.com/owncloud/ocis-proxy/issues/17)
## Details
* Change - Initial release of basic version: [#1](https://github.com/owncloud/ocis-proxy/issues/1)
Just prepared an initial basic version.
https://github.com/owncloud/ocis-proxy/issues/1
* Enhancement - Load Proxy Policies at Runtime: [#17](https://github.com/owncloud/ocis-proxy/issues/17)
While a proxy without policies is of no use, the current state of ocis-proxy expects a config
file either at an expected Viper location or specified via -- config-file flag. To ease
deployments and ensure a working set of policies out of the box we need a series of defaults.
https://github.com/owncloud/ocis-proxy/issues/17
https://github.com/owncloud/ocis-proxy/pull/16

202
proxy/LICENSE Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

156
proxy/Makefile Normal file
View File

@@ -0,0 +1,156 @@
SHELL := bash
NAME := ocis-proxy
IMPORT := github.com/owncloud/$(NAME)
BIN := bin
DIST := dist
HUGO := hugo
ifeq ($(OS), Windows_NT)
EXECUTABLE := $(NAME).exe
UNAME := Windows
else
EXECUTABLE := $(NAME)
UNAME := $(shell uname -s)
endif
ifeq ($(UNAME), Darwin)
GOBUILD ?= go build -i
else
GOBUILD ?= go build
endif
PACKAGES ?= $(shell go list ./...)
SOURCES ?= $(shell find . -name "*.go" -type f -not -path "./node_modules/*")
GENERATE ?= $(PACKAGES)
TAGS ?=
ifndef OUTPUT
ifneq ($(DRONE_TAG),)
OUTPUT ?= $(subst v,,$(DRONE_TAG))
else
OUTPUT ?= testing
endif
endif
ifndef VERSION
ifneq ($(DRONE_TAG),)
VERSION ?= $(subst v,,$(DRONE_TAG))
else
VERSION ?= $(shell git rev-parse --short HEAD)
endif
endif
ifndef DATE
DATE := $(shell date -u '+%Y%m%d')
endif
LDFLAGS += -s -w -X "$(IMPORT)/pkg/version.String=$(VERSION)" -X "$(IMPORT)/pkg/version.Date=$(DATE)"
DEBUG_LDFLAGS += -X "$(IMPORT)/pkg/version.String=$(VERSION)" -X "$(IMPORT)/pkg/version.Date=$(DATE)"
GCFLAGS += all=-N -l
.PHONY: all
all: build
.PHONY: sync
sync:
go mod download
.PHONY: clean
clean:
go clean -i ./...
rm -rf $(BIN) $(DIST) $(HUGO)
.PHONY: fmt
fmt:
gofmt -s -w $(SOURCES)
.PHONY: vet
vet:
go vet $(PACKAGES)
.PHONY: lint
lint:
for PKG in $(PACKAGES); do go run golang.org/x/lint/golint -set_exit_status $$PKG || exit 1; done;
.PHONY: generate
generate:
go generate $(GENERATE)
.PHONY: changelog
changelog:
go run github.com/restic/calens >| CHANGELOG.md
.PHONY: test
test:
go run github.com/haya14busa/goverage -v -coverprofile coverage.out $(PACKAGES)
.PHONY: install
install: $(SOURCES)
go install -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/$(NAME)
.PHONY: build
build: $(BIN)/$(EXECUTABLE) $(BIN)/$(EXECUTABLE)-debug
$(BIN)/$(EXECUTABLE): $(SOURCES)
$(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME)
$(BIN)/$(EXECUTABLE)-debug: $(SOURCES)
$(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -gcflags '$(GCFLAGS)' -o $@ ./cmd/$(NAME)
.PHONY: release
release: release-dirs release-linux release-windows release-darwin release-copy release-check
.PHONY: release-dirs
release-dirs:
mkdir -p $(DIST)/binaries $(DIST)/release
.PHONY: release-linux
release-linux: release-dirs
go run github.com/mitchellh/gox -tags 'netgo $(TAGS)' -ldflags '-extldflags "-static" $(LDFLAGS)' -os 'linux' -arch 'amd64 386 arm64 arm' -output '$(DIST)/binaries/$(EXECUTABLE)-$(OUTPUT)-{{.OS}}-{{.Arch}}' ./cmd/$(NAME)
.PHONY: release-windows
release-windows: release-dirs
go run github.com/mitchellh/gox -tags 'netgo $(TAGS)' -ldflags '-extldflags "-static" $(LDFLAGS)' -os 'windows' -arch 'amd64' -output '$(DIST)/binaries/$(EXECUTABLE)-$(OUTPUT)-{{.OS}}-{{.Arch}}' ./cmd/$(NAME)
.PHONY: release-darwin
release-darwin: release-dirs
go run github.com/mitchellh/gox -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -os 'darwin' -arch 'amd64' -output '$(DIST)/binaries/$(EXECUTABLE)-$(OUTPUT)-{{.OS}}-{{.Arch}}' ./cmd/$(NAME)
.PHONY: release-copy
release-copy:
$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
.PHONY: release-check
release-check:
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
.PHONY: release-finish
release-finish: release-copy release-check
.PHONY: docs-copy
docs-copy:
mkdir -p $(HUGO); \
mkdir -p $(HUGO)/content/extensions; \
cd $(HUGO); \
git init; \
git remote rm origin; \
git remote add origin https://github.com/owncloud/owncloud.github.io; \
git fetch; \
git checkout origin/source -f; \
rsync --delete -ax ../docs/ content/extensions/$(NAME)
.PHONY: config-docs-generate
config-docs-generate:
go run github.com/owncloud/flaex >| docs/configuration.md
.PHONY: docs-build
docs-build:
cd $(HUGO); hugo
.PHONY: docs
docs: config-docs-generate docs-copy docs-build
.PHONY: watch
watch:
go run github.com/cespare/reflex -c reflex.conf

45
proxy/README.md Normal file
View File

@@ -0,0 +1,45 @@
# ownCloud Infinite Scale: Proxy
[![Build Status](https://cloud.drone.io/api/badges/owncloud/ocis-proxy/status.svg)](https://cloud.drone.io/owncloud/ocis-proxy)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/636af6e2270e4c7ca0f3eb2efc814c21)](https://www.codacy.com/gh/owncloud/ocis-proxy?utm_source=github.com&utm_medium=referral&utm_content=owncloud/ocis-bridge&utm_campaign=Badge_Grade)
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/636af6e2270e4c7ca0f3eb2efc814c21)](https://www.codacy.com/gh/owncloud/ocis-proxy?utm_source=github.com&utm_medium=referral&utm_content=owncloud/ocis-bridge&utm_campaign=Badge_Coverage)
[![Go Doc](https://godoc.org/github.com/owncloud/ocis-proxy?status.svg)](http://godoc.org/github.com/owncloud/ocis-proxy)
[![Go Report](http://goreportcard.com/badge/github.com/owncloud/ocis-proxy)](http://goreportcard.com/report/github.com/owncloud/ocis-proxy)
[![](https://images.microbadger.com/badges/image/owncloud/ocis-proxy.svg)](http://microbadger.com/images/owncloud/ocis-proxy "Get your own image badge on microbadger.com")
**This project is under heavy development, it's not in a working state yet!**
## Install
You can download prebuilt binaries from the GitHub releases or from our [download mirrors](http://download.owncloud.com/ocis/proxy/). For instructions how to install this on your platform you should take a look at our [documentation](https://owncloud.github.io/ocis-proxy/)
****
## Development
Make sure you have a working Go environment, for further reference or a guide take a look at the [install instructions](http://golang.org/doc/install.html). This project requires Go >= v1.13.
```console
git clone https://github.com/owncloud/ocis-proxy.git
cd ocis-proxy
make generate build
./bin/ocis-proxy -h
```
## Security
If you find a security issue please contact security@owncloud.com first.
## Contributing
Fork -> Patch -> Push -> Pull Request
## License
Apache-2.0
## Copyright
```console
Copyright (c) 2019 ownCloud GmbH <https://owncloud.com>
```

View File

@@ -0,0 +1,5 @@
Change: Initial release of basic version
Just prepared an initial basic version.
https://github.com/owncloud/ocis-proxy/issues/1

View File

@@ -0,0 +1,7 @@
Enhancement: Load Proxy Policies at Runtime
While a proxy without policies is of no use, the current state of ocis-proxy expects a config file either at an expected Viper location or specified via -- config-file flag.
To ease deployments and ensure a working set of policies out of the box we need a series of defaults.
https://github.com/owncloud/ocis-proxy/issues/17
https://github.com/owncloud/ocis-proxy/pull/16

View File

@@ -0,0 +1,6 @@
Change: Route requests based on regex or query parameters
Some requests needed to be distinguished based on a pattern or a query parameter.
We've implemented the functionality to route requests based on different conditions.
https://github.com/owncloud/ocis-proxy/issues/21

View File

@@ -0,0 +1,5 @@
Enhancement: Proxy client urls in default configuration
Proxy /status.php and index.php/*
https://github.com/owncloud/ocis-proxy/issues/19

View File

@@ -0,0 +1,5 @@
Enhancement: Make TLS-Cert configurable
Before a generates certificates on every start was used for dev purposes.
https://github.com/owncloud/ocis-proxy/pull/14

View File

@@ -0,0 +1,3 @@
Bugfix: Set TLS-Certificate correctly
https://github.com/owncloud/ocis-proxy/pull/25

View File

@@ -0,0 +1,8 @@
Enhancement: Configurable OpenID Connect client
The proxy will try to authenticate every request with the configured OIDC provider.
See configs/proxy-example.oidc.json for an example-configuration.
https://github.com/owncloud/ocis-proxy/pull/27

View File

@@ -0,0 +1,10 @@
Enhancement: Add policy selectors
"Static-Policy" can be configured to always select a specific policy.
See: config/proxy-example.json.
"Migration-Policy" selects policy depending on existence of the uid in the ocis-accounts service.
See: config/proxy-example-migration.json
https://github.com/owncloud/ocis-proxy/issues/4

View File

@@ -0,0 +1,4 @@
Change: Insecure http-requests are now redirected to https
https://github.com/owncloud/ocis-proxy/pull/29

View File

@@ -0,0 +1,5 @@
Change: Update ocis-pkg
We updated ocis-pkg from 2.0.2 to 2.2.0.
https://github.com/owncloud/ocis-proxy/pull/30

View File

@@ -0,0 +1,5 @@
Enhancement: Retrieve Account UUID From User Claims
OIDC Middleware can make use of uuidFromClaims to trade claims.Email for an account's UUID. For this, a general purpose cache was added that caches on a per-request basis, meaning whenever the request parameters match a set of keys, the cached value is returned, saving a round trip to the accounts service that otherwise would happen in every single request.
https://github.com/owncloud/ocis-proxy/pull/36

View File

@@ -0,0 +1,7 @@
Enhancement: create account if it doesn't exist in ocis-accounts
The accounts_uuid middleware tries to get the account from ocis-accounts.
If it doens't exist there yet the proxy creates the account using the ocis-account api.
https://github.com/owncloud/ocis-proxy/issues/55
https://github.com/owncloud/ocis-proxy/issues/58

View File

@@ -0,0 +1,6 @@
Bugfix: Accounts service response was ignored
We fixed an error in the AccountUUID middleware that was responsible for ignoring an account uuid
provided by the accounts service.
https://github.com/owncloud/ocis-proxy/pull/43

View File

@@ -0,0 +1,7 @@
Enhancement: Disable keep-alive on server-side OIDC requests
This should reduce file-descriptor counts
https://github.com/owncloud/ocis/issues/268
https://github.com/owncloud/ocis-proxy/pull/42
https://github.com/cs3org/reva/pull/787

View File

@@ -0,0 +1,5 @@
Enhancement: Make jwt secret configurable
We added a config option for the reva token manager JWTSecret. It was hardcoded before and is now configurable.
https://github.com/owncloud/ocis-proxy/pull/41

View File

@@ -0,0 +1,5 @@
Change: Point /data endpoint to reva frontend
Adjusted example config files to point /data to the reva frontend.
https://github.com/owncloud/ocis-proxy/pull/45

View File

@@ -0,0 +1,6 @@
Enhancement: respect account_enabled flag
If the account returned by the accounts service has the account_enabled flag
set to false, the proxy will return immediately with the status code unauthorized.
https://github.com/owncloud/ocis-proxy/issues/53

View File

@@ -0,0 +1,5 @@
Change: Send autocreate home request to reva gateway
Send autocreate home request to reva gateway
https://github.com/owncloud/ocis-proxy/pull/51

View File

@@ -0,0 +1,6 @@
Bugfix: Fix x-access-token in header
We fixed setting the x-access-token in the request header, which was broken before.
https://github.com/owncloud/ocis-proxy/pull/41
https://github.com/owncloud/ocis-proxy/pull/46

View File

@@ -0,0 +1,5 @@
Change: Update to new accounts API
Update to new accounts API
https://github.com/owncloud/ocis-proxy/issues/39

View File

@@ -0,0 +1,9 @@
Enhancement: Add Accounts UI routes
The accounts service has a ui that requires routing
- `/api/v0/accounts` and
- `/accounts.js`
to http://localhost:9181
https://github.com/owncloud/ocis-proxy/pull/65

View File

@@ -0,0 +1,9 @@
Enhancement: Add option to disable TLS
Can be used to disable TLS when the ocis-proxy is behind an
TLS-Terminating reverse proxy.
env PROXY_TLS=false or --tls=false
https://github.com/owncloud/ocis-proxy/issues/71
https://github.com/owncloud/ocis-proxy/pull/72

View File

@@ -0,0 +1,11 @@
Change: Add OIDC config flags
To authenticate requests with an oidc provider we added two environment variables:
- `PROXY_OIDC_ISSUER="https://localhost:9200"` and
- `PROXY_OIDC_INSECURE=true`
This changes ocis-proxy to now load the oidc-middleware by default, requiring a bearer token and exchanging the email in the OIDC claims for an account id at the ocis-accounts service.
Setting `PROXY_OIDC_ISSUER=""` will disable the OIDC middleware.
https://github.com/owncloud/ocis-proxy/pull/66

View File

@@ -0,0 +1,7 @@
Enhancement: only send create home request if an account has been migrated
This change adds a check if an account has been migrated by getting it from the
ocis-accounts service. If no account is returned it means it hasn't been migrated.
https://github.com/owncloud/ocis-proxy/issues/52
https://github.com/owncloud/ocis-proxy/pull/63

View File

@@ -0,0 +1,5 @@
Bugfix: Provide token configuration from config
Fixed a bug that causes the createHome middleware to crash if no configuration for the TokenManager is propagated.
https://github.com/owncloud/ocis-proxy/pull/69

View File

@@ -0,0 +1,5 @@
Bugfix: Provide token configuration from config
Fixed a bug that causes the createHome middleware to crash if the createHome response has no Status set
https://github.com/owncloud/ocis-proxy/pull/76

View File

@@ -0,0 +1,5 @@
Enhancement: Create a root span on proxy that propagates down to consumers
In order to propagate and correctly associate a span with a request we need a root span that gets sent to other services.
https://github.com/owncloud/ocis-proxy/pull/64

View File

@@ -0,0 +1,8 @@
Enhancement: Support signed URLs
We added a middleware that verifies signed urls as generated by the owncloud-sdk. This allows directly downloading large files with browsers instead of using `blob://` urls, which eats memory ...
https://github.com/owncloud/ocis-proxy/issues/73
https://github.com/owncloud/ocis-proxy/pull/75
https://github.com/owncloud/ocis-ocs/pull/18
https://github.com/owncloud/owncloud-sdk/pull/504

View File

@@ -0,0 +1,6 @@
Change: mint new username property in the reva token
An accounts username is now taken from the on_premises_sam_account_name property instead of the preferred_name.
Furthermore the group name (also from on_premises_sam_account_name property) is now minted into the token as well.
https://github.com/owncloud/ocis-proxy/pull/62

View File

@@ -0,0 +1,5 @@
Bugfix: enable new accounts by default
When new accounts are created, they also need to be enabled to be useable.
https://github.com/owncloud/ocis-proxy/pull/79

View File

@@ -0,0 +1,7 @@
Bugfix: Lookup user by id for presigned URLs
Phoenix will send the `userid`, not the `username` as the `OC-Credential` for presigned URLs. This PR uses the new `ocisid` claim in the OIDC userinfo to pass the userid to the account middleware.
https://github.com/owncloud/ocis-proxy/pull/85
https://github.com/owncloud/ocis-pkg/pull/50
https://github.com/owncloud/ocis/issues/436

View File

@@ -0,0 +1,5 @@
Change: add settings and ocs group routes
Route settings requests and ocs group related requests to new services
https://github.com/owncloud/ocis-proxy/pull/81

View File

@@ -0,0 +1,5 @@
Bugfix: build docker images with alpine:latest instead of alpine:edge
ARM builds were failing when built on alpine:edge, so we switched to alpine:latest instead.
https://github.com/owncloud/ocis-proxy/pull/78

View File

@@ -0,0 +1,5 @@
Change: Add route for user provisioning API in ocis-ocs
We added a route to send requests on the user provisioning API endpoints to ocis-ocs.
https://github.com/owncloud/ocis-proxy/pull/80

View File

@@ -0,0 +1,5 @@
Enhancement: Add numeric uid and gid to the access token
The eos storage driver is fetching the uid and gid of a user from the access token. This PR is using the response of the accounts service to mint them in the token.
https://github.com/owncloud/ocis-proxy/pull/89

View File

@@ -0,0 +1,7 @@
Enhancement: add configuration options for the pre-signed url middleware
Added an option to define allowed http methods for pre-signed url requests.
This is useful since we only want clients to GET resources and don't upload anything with presigned requests.
https://github.com/owncloud/ocis-proxy/issues/91
https://github.com/owncloud/product/issues/150

View File

@@ -0,0 +1,53 @@
{{ $allVersions := . }}
{{- range $index, $changes := . }}{{ with $changes -}}
{{ if gt (len $allVersions) 1 -}}
# Changelog for [{{ .Version }}] ({{ .Date }})
The following sections list the changes for ocis-proxy {{ .Version }}.
{{/* creating version compare links */ -}}
{{ $next := add1 $index -}}
{{ if ne (len $allVersions) $next -}}
{{ $previousVersion := (index $allVersions $next).Version -}}
{{ if eq .Version "unreleased" -}}
[{{ .Version }}]: https://github.com/owncloud/ocis-proxy/compare/v{{ $previousVersion }}...master
{{ else -}}
[{{ .Version }}]: https://github.com/owncloud/ocis-proxy/compare/v{{ $previousVersion }}...v{{ .Version }}
{{ end -}}
{{ end -}}
{{- /* last version managed by calens, end of the loop */ -}}
{{ if eq .Version "0.1.0" -}}
[{{ .Version }}]: https://github.com/owncloud/ocis-proxy/compare/500e303cb544ed93d84153f01219d77eeee44929...v{{ .Version }}
{{ end -}}
{{ else -}}
# Changes in {{ .Version }}
{{ end -}}
## Summary
{{ range $entry := .Entries }}{{ with $entry }}
* {{ .Type }} - {{ .Title }}: [#{{ .PrimaryID }}]({{ .PrimaryURL }})
{{- end }}{{ end }}
## Details
{{ range $entry := .Entries }}{{ with $entry }}
* {{ .Type }} - {{ .Title }}: [#{{ .PrimaryID }}]({{ .PrimaryURL }})
{{ range $par := .Paragraphs }}
{{ wrapIndent $par 80 3 }}
{{ end -}}
{{ range $url := .IssueURLs }}
{{ $url -}}
{{ end -}}
{{ range $url := .PRURLs }}
{{ $url -}}
{{ end -}}
{{ range $url := .OtherURLs }}
{{ $url -}}
{{ end }}
{{ end }}{{ end -}}
{{ end }}{{ end -}}

View File

@@ -0,0 +1,6 @@
# Changelog
We are using [calens](https://github.com/restic/calens) to properly generate a
changelog before we are tagging a new release. To get an idea how this could
look like <https://github.com/restic/restic/tree/master/changelog> would be the
best reference.

11
proxy/changelog/TEMPLATE Normal file
View File

@@ -0,0 +1,11 @@
Bugfix: Fix behavior for foobar (in present tense)
We've fixed the behavior for foobar, a long-standing annoyance for users. The
text should be wrapped at 80 characters length.
The text in the paragraphs is written in past tense. The last section is a list
of issue URLs, PR URLs and other URLs. The first issue ID (or the first PR ID,
in case there aren't any issue links) is used as the primary ID.
https://github.com/owncloud/ocis-proxy/issues/1234
https://github.com/owncloud/ocis-proxy/pull/55555

View File

View File

@@ -0,0 +1,5 @@
Bugfix: Fix director selection
We fixed a bug where simultaneous requests could be executed on the wrong backend.
https://github.com/owncloud/ocis-proxy/pull/99

View File

@@ -0,0 +1,5 @@
Enhancement: Add hello API and app endpoints to example config and builtin config
We added the ocis-hello API and app endpoints to both the example config and the builtin config.
https://github.com/owncloud/ocis-proxy/pull/96

View File

@@ -0,0 +1,5 @@
Enhancement: Add roleIDs to the access token
We are using the roleIDs of the authenticated user for permission checks against ocis-settings. We added the roleIDs to the access token to have them available quickly.
https://github.com/owncloud/ocis-proxy/pull/95

View File

@@ -0,0 +1,5 @@
Change: Remove accounts caching
We removed the accounts cache in order to avoid problems with accounts that have been updated in the accounts service.
https://github.com/owncloud/ocis-proxy/pull/100

View File

@@ -0,0 +1,6 @@
Bugfix: Add settings API and app endpoints to example config
We had the ocis-settings API and app endpoints in the builtin config already, but they were missing in the example
config. Added them for consistency.
https://github.com/owncloud/ocis-proxy/pull/93

View File

@@ -0,0 +1,13 @@
package main
import (
"os"
"github.com/owncloud/ocis-proxy/pkg/command"
)
func main() {
if err := command.Execute(); err != nil {
os.Exit(1)
}
}

View File

@@ -0,0 +1,137 @@
{
"HTTP": {
"Namespace": "com.owncloud"
},
"oidc": {
"issuer": "https://localhost:9200",
"insecure": true
},
"policy_selector": {
"migration": {
"acc_found_policy" : "reva",
"acc_not_found_policy": "oc10",
"unauthenticated_policy": "oc10"
}
},
"policies": [
{
"name": "reva",
"routes": [
{
"endpoint": "/",
"backend": "http://localhost:9100"
},
{
"endpoint": "/.well-known/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/konnect/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/signin/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/ocs/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/remote.php/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/dav/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/webdav/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/status.php",
"backend": "http://localhost:9140"
},
{
"endpoint": "/index.php/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/data",
"backend": "http://localhost:9140"
},
{
"endpoint": "/api/v0/accounts",
"backend": "http://localhost:9181"
},
{
"endpoint": "/accounts.js",
"backend": "http://localhost:9181"
},
{
"endpoint": "/api/v0/settings",
"backend": "http://localhost:9190"
},
{
"endpoint": "/settings.js",
"backend": "http://localhost:9190"
}
]
},
{
"name": "oc10",
"routes": [
{
"endpoint": "/",
"backend": "http://localhost:9100"
},
{
"endpoint": "/.well-known/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/konnect/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/signin/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/ocs/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/remote.php/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/dav/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/webdav/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/status.php",
"backend": "https://demo.owncloud.com"
},
{
"endpoint": "/index.php/",
"backend": "https://demo.owncloud.com"
},
{
"endpoint": "/data",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
}
]
}
]
}

View File

@@ -0,0 +1,148 @@
{
"HTTP": {
"Namespace": "com.owncloud"
},
"oidc": {
"issuer": "https://localhost:9200",
"insecure": true
},
"policy_selector": {
"static": {
"policy": "reva"
}
},
"policies": [
{
"name": "reva",
"routes": [
{
"endpoint": "/",
"backend": "http://localhost:9100"
},
{
"endpoint": "/.well-known/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/konnect/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/signin/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/ocs/",
"backend": "http://localhost:9140"
},
{
"type": "regex",
"endpoint": "/ocs/v[12].php/cloud/user",
"backend": "http://localhost:9110"
},
{
"endpoint": "/remote.php/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/dav/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/webdav/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/status.php",
"backend": "http://localhost:9140"
},
{
"endpoint": "/index.php/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/data",
"backend": "http://localhost:9140"
},
{
"endpoint": "/api/v0/accounts",
"backend": "http://localhost:9181"
},
{
"endpoint": "/accounts.js",
"backend": "http://localhost:9181"
},
{
"endpoint": "/api/v0/settings",
"backend": "http://localhost:9190"
},
{
"endpoint": "/settings.js",
"backend": "http://localhost:9190"
},
{
"endpoint": "/api/v0/greet",
"backend": "http://localhost:9105"
},
{
"endpoint": "/hello.js",
"backend": "http://localhost:9105"
}
]
},
{
"name": "oc10",
"routes": [
{
"endpoint": "/",
"backend": "http://localhost:9100"
},
{
"endpoint": "/.well-known/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/konnect/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/signin/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/ocs/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/remote.php/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/dav/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/webdav/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/status.php",
"backend": "https://demo.owncloud.com"
},
{
"endpoint": "/index.php/",
"backend": "https://demo.owncloud.com"
},
{
"endpoint": "/data",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
}
]
}
]
}

View File

@@ -0,0 +1,19 @@
FROM amd64/alpine:latest
RUN apk update && \
apk upgrade && \
apk add ca-certificates mailcap && \
rm -rf /var/cache/apk/* && \
echo 'hosts: files dns' >| /etc/nsswitch.conf
LABEL maintainer="ownCloud GmbH <devops@owncloud.com>" \
org.label-schema.name="oCIS Proxy" \
org.label-schema.vendor="ownCloud GmbH" \
org.label-schema.schema-version="1.0"
EXPOSE 9180
ENTRYPOINT ["/usr/bin/ocis-proxy"]
CMD ["server"]
COPY bin/ocis-proxy /usr/bin/ocis-proxy

View File

@@ -0,0 +1,19 @@
FROM arm32v6/alpine:latest
RUN apk update && \
apk upgrade && \
apk add ca-certificates mailcap && \
rm -rf /var/cache/apk/* && \
echo 'hosts: files dns' >| /etc/nsswitch.conf
LABEL maintainer="ownCloud GmbH <devops@owncloud.com>" \
org.label-schema.name="oCIS Proxy" \
org.label-schema.vendor="ownCloud GmbH" \
org.label-schema.schema-version="1.0"
EXPOSE 9180
ENTRYPOINT ["/usr/bin/ocis-proxy"]
CMD ["server"]
COPY bin/ocis-proxy /usr/bin/ocis-proxy

View File

@@ -0,0 +1,19 @@
FROM arm64v8/alpine:latest
RUN apk update && \
apk upgrade && \
apk add ca-certificates mailcap && \
rm -rf /var/cache/apk/* && \
echo 'hosts: files dns' >| /etc/nsswitch.conf
LABEL maintainer="ownCloud GmbH <devops@owncloud.com>" \
org.label-schema.name="oCIS Proxy" \
org.label-schema.vendor="ownCloud GmbH" \
org.label-schema.schema-version="1.0"
EXPOSE 9180
ENTRYPOINT ["/usr/bin/ocis-proxy"]
CMD ["server"]
COPY bin/ocis-proxy /usr/bin/ocis-proxy

View File

@@ -0,0 +1,22 @@
image: owncloud/ocis-proxy:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
- image: owncloud/ocis-proxy:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
- image: owncloud/ocis-proxy:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
platform:
architecture: arm64
variant: v8
os: linux
- image: owncloud/ocis-proxy:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
platform:
architecture: arm
variant: v6
os: linux

8
proxy/docs/_index.md Normal file
View File

@@ -0,0 +1,8 @@
---
title: Proxy
geekdocRepo: https://github.com/owncloud/ocis-proxy
geekdocEditPath: edit/master/docs
geekdocFilePath: _index.md
---
This service provides a basic proxy in front of the public ocis services.

10
proxy/docs/about.md Normal file
View File

@@ -0,0 +1,10 @@
---
title: "About"
date: 2020-02-07T00:00:00+00:00
weight: 10
geekdocRepo: https://github.com/owncloud/ocis-proxy
geekdocEditPath: edit/master/docs
geekdocFilePath: about.md
---
This service provides an proxy service that routes requests to the correct services.

28
proxy/docs/building.md Normal file
View File

@@ -0,0 +1,28 @@
---
title: "Building"
date: 2018-05-02T00:00:00+00:00
weight: 30
geekdocRepo: https://github.com/owncloud/ocis-proxy
geekdocEditPath: edit/master/docs
geekdocFilePath: building.md
---
{{< toc >}}
As this project is built with Go, so you need to install that first. The installation of Go is out of the scope of this document, please follow the official documentation for [Go](https://golang.org/doc/install), to build this project you have to install Go >= v1.13. After the installation of the required tools you need to get the sources:
{{< highlight txt >}}
git clone https://github.com/owncloud/ocis-proxy.git
cd ocis-proxy
{{< / highlight >}}
All required tool besides Go itself and make are bundled or getting automatically installed within the `GOPATH`. All commands to build this project are part of our `Makefile`.
## Backend
{{< highlight txt >}}
make generate
make build
{{< / highlight >}}
Finally you should have the binary within the `bin/` folder now, give it a try with `./bin/ocis-proxy -h` to see all available options and subcommands.

119
proxy/docs/configuration.md Normal file
View File

@@ -0,0 +1,119 @@
---
title: "Configuration"
date: "2020-04-20T21:24:14+0200"
weight: 20
geekdocRepo: https://github.com/owncloud/ocis-proxy
geekdocEditPath: edit/master/docs
geekdocFilePath: configuration.md
---
{{< toc >}}
## Configuration
oCIS Single Binary is not responsible for configuring extensions. Instead, each extension could either be configured by environment variables, cli flags or config files.
Each extension has its dedicated documentation page (e.g. https://owncloud.github.io/extensions/ocis_proxy/configuration) which lists all possible configurations. Config files and environment variables are picked up if you use the `./bin/ocis server` command within the oCIS single binary. Command line flags must be set explicitly on the extensions subcommands.
### Configuration using config files
Out of the box extensions will attempt to read configuration details from:
```console
/etc/ocis
$HOME/.ocis
./config
```
For this configuration to be picked up, have a look at your extension `root` command and look for which default config name it has assigned. *i.e: ocis-proxy reads `proxy.json | yaml | toml ...`*.
So far we support the file formats `JSON` and `YAML`, if you want to get a full example configuration just take a look at [our repository](https://github.com/owncloud/ocis/tree/master/config), there you can always see the latest configuration format. These example configurations include all available options and the default values. The configuration file will be automatically loaded if it's placed at `/etc/ocis/ocis.yml`, `${HOME}/.ocis/ocis.yml` or `$(pwd)/config/ocis.yml`.
### Envrionment variables
If you prefer to configure the service with environment variables you can see the available variables below.
### Commandline flags
If you prefer to configure the service with commandline flags you can see the available variables below. Command line flags are only working when calling the subcommand directly.
## Root Command
proxy for Reva/oCIS
Usage: `ocis-proxy [global options] command [command options] [arguments...]`
--config-file | $PROXY_CONFIG_FILE
: Path to config file.
--log-level | $PROXY_LOG_LEVEL
: Set logging level. Default: `info`.
--log-pretty | $PROXY_LOG_PRETTY
: Enable pretty logging. Default: `true`.
--log-color | $PROXY_LOG_COLOR
: Enable colored logging. Default: `true`.
## Sub Commands
### ocis-proxy server
Start integrated server
Usage: `ocis-proxy server [command options] [arguments...]`
--tracing-enabled | $PROXY_TRACING_ENABLED
: Enable sending traces.
--tracing-type | $PROXY_TRACING_TYPE
: Tracing backend type. Default: `jaeger`.
--tracing-endpoint | $PROXY_TRACING_ENDPOINT
: Endpoint for the agent.
--tracing-collector | $PROXY_TRACING_COLLECTOR
: Endpoint for the collector.
--tracing-service | $PROXY_TRACING_SERVICE
: Service name for tracing. Default: `proxy`.
--debug-addr | $PROXY_DEBUG_ADDR
: Address to bind debug server. Default: `0.0.0.0:9205`.
--debug-token | $PROXY_DEBUG_TOKEN
: Token to grant metrics access.
--debug-pprof | $PROXY_DEBUG_PPROF
: Enable pprof debugging.
--debug-zpages | $PROXY_DEBUG_ZPAGES
: Enable zpages debugging.
--http-addr | $PROXY_HTTP_ADDR
: Address to bind http server. Default: `0.0.0.0:9200`.
--http-root | $PROXY_HTTP_ROOT
: Root path of http server. Default: `/`.
--asset-path | $PROXY_ASSET_PATH
: Path to custom assets.
--http-namespace | $PROXY_HTTP_NAMESPACE
: Set the base namespace for the http namespace. Default: `com.owncloud`.
--transport-tls-cert | $PROXY_TRANSPORT_TLS_CERT
: Certificate file for transport encryption.
--transport-tls-key | $PROXY_TRANSPORT_TLS_KEY
: Secret file for transport encryption.
### ocis-proxy health
Check health status
Usage: `ocis-proxy health [command options] [arguments...]`
--debug-addr | $PROXY_DEBUG_ADDR
: Address to debug endpoint. Default: `0.0.0.0:9109`.

View File

@@ -0,0 +1,46 @@
---
title: "Getting Started"
date: 2018-05-02T00:00:00+00:00
weight: 15
geekdocRepo: https://github.com/owncloud/ocis-proxy
geekdocEditPath: edit/master/docs
geekdocFilePath: getting-started.md
---
{{< toc >}}
## Installation
So far we are offering two different variants for the installation. You can choose between [Docker](https://www.docker.com/) or pre-built binaries which are stored on our download mirrors and GitHub releases. Maybe we will also provide system packages for the major distributions later if we see the need for it.
### Docker
Docker images for ocis-reva are hosted on https://hub.docker.com/r/owncloud/ocis-proxy.
The `latest` tag always reflects the current master branch.
```console
docker pull owncloud/ocis-proxy
```
### Binaries
The pre-built binaries for different platforms are downloadable at https://download.owncloud.com/ocis/ocis-proxy/ . Specific releases are organized in separate folders. They are in sync which every release tag on GitHub. The binaries from the current master branch can be found in https://download.owncloud.com/ocis/ocis-proxy/testing/
```console
curl https://download.owncloud.com/ocis/ocis-proxy/1.0.0-beta1/ocis-proxy-1.0.0-beta1-darwin-amd64 --output ocis-proxy
chmod +x ocis-proxy
./ocis-proxy server
```
## Usage
The program provides a few sub-commands on execution. The available configuration methods have already been mentioned above. Generally you can always see a formated help output if you execute the binary via `ocis-proxy --help`.
### Server
The server command is used to start the http server. For further help please execute:
{{< highlight txt >}}
ocis-proxy server --help
{{< / highlight >}}

31
proxy/go.mod Normal file
View File

@@ -0,0 +1,31 @@
module github.com/owncloud/ocis-proxy
go 1.13
require (
contrib.go.opencensus.io/exporter/jaeger v0.2.1
contrib.go.opencensus.io/exporter/ocagent v0.7.0
contrib.go.opencensus.io/exporter/zipkin v0.1.1
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/cs3org/go-cs3apis v0.0.0-20200730121022-c4f3d4f7ddfd
github.com/cs3org/reva v1.1.0
github.com/justinas/alice v1.2.0
github.com/micro/cli/v2 v2.1.2
github.com/micro/go-micro/v2 v2.9.1
github.com/oklog/run v1.1.0
github.com/openzipkin/zipkin-go v0.2.2
github.com/owncloud/flaex v0.2.0
github.com/owncloud/ocis-accounts v0.1.2-0.20200618163128-aa8ae58dd95e
github.com/owncloud/ocis-pkg/v2 v2.4.0
github.com/owncloud/ocis-settings v0.3.2-0.20200828130413-0cc0f5bf26fe
github.com/owncloud/ocis-store v0.0.0-20200716140351-f9670592fb7b
github.com/prometheus/client_golang v1.7.1
github.com/restic/calens v0.2.0
github.com/spf13/viper v1.7.0
go.opencensus.io v0.22.4
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
google.golang.org/grpc v1.31.0
)
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0

1872
proxy/go.sum Normal file

File diff suppressed because it is too large Load Diff

101
proxy/pkg/cache/cache.go vendored Normal file
View File

@@ -0,0 +1,101 @@
package cache
import (
"fmt"
"sync"
)
// Entry represents an entry on the cache. You can type assert on V.
type Entry struct {
V interface{}
Valid bool
}
// Cache is a barebones cache implementation.
type Cache struct {
entries map[string]map[string]Entry
size int
m sync.Mutex
}
// NewCache returns a new instance of Cache.
func NewCache(o ...Option) Cache {
opts := newOptions(o...)
return Cache{
size: opts.size,
entries: map[string]map[string]Entry{},
}
}
// Get gets an entry on a service `svcKey` by a give `key`.
func (c *Cache) Get(svcKey, key string) (*Entry, error) {
var value Entry
ok := true
c.m.Lock()
defer c.m.Unlock()
if value, ok = c.entries[svcKey][key]; !ok {
return nil, fmt.Errorf("invalid service key: `%v`", key)
}
return &value, nil
}
// Set sets a key / value. It lets a service add entries on a request basis.
func (c *Cache) Set(svcKey, key string, val interface{}) error {
c.m.Lock()
defer c.m.Unlock()
if !c.fits() {
return fmt.Errorf("cache is full")
}
if _, ok := c.entries[svcKey]; !ok {
c.entries[svcKey] = map[string]Entry{}
}
if _, ok := c.entries[svcKey][key]; ok {
return fmt.Errorf("key `%v` already exists", key)
}
c.entries[svcKey][key] = Entry{
V: val,
Valid: true,
}
return nil
}
// Invalidate invalidates a cache Entry by key.
func (c *Cache) Invalidate(svcKey, key string) error {
r, err := c.Get(svcKey, key)
if err != nil {
return err
}
r.Valid = false
c.entries[svcKey][key] = *r
return nil
}
// Evict frees memory from the cache by removing invalid keys. It is a noop.
func (c *Cache) Evict() {
for _, v := range c.entries {
for k, svcEntry := range v {
if !svcEntry.Valid {
delete(v, k)
}
}
}
}
// Length returns the amount of entries per service key.
func (c *Cache) Length(k string) int {
return len(c.entries[k])
}
func (c *Cache) fits() bool {
return c.size >= len(c.entries)
}

99
proxy/pkg/cache/cache_test.go vendored Normal file
View File

@@ -0,0 +1,99 @@
package cache
import (
"testing"
)
// Prevents from invalid import cycle.
type AccountsCacheEntry struct {
Email string
UUID string
}
func TestSet(t *testing.T) {
c := NewCache(
Size(256),
)
err := c.Set("accounts", "hello@foo.bar", AccountsCacheEntry{
Email: "hello@foo.bar",
UUID: "9c31b040-59e2-4a2b-926b-334d9e3fbd05",
})
if err != nil {
t.Error(err)
}
if c.Length("accounts") != 1 {
t.Errorf("expected length 1 got `%v`", len(c.entries))
}
item, err := c.Get("accounts", "hello@foo.bar")
if err != nil {
t.Error(err)
}
if cachedEntry, ok := item.V.(AccountsCacheEntry); !ok {
t.Errorf("invalid cached value type")
} else {
if cachedEntry.Email != "hello@foo.bar" {
t.Errorf("invalid value. Expected `hello@foo.bar` got: `%v`", cachedEntry.Email)
}
}
}
func TestEvict(t *testing.T) {
c := NewCache(
Size(256),
)
if err := c.Set("accounts", "hello@foo.bar", AccountsCacheEntry{
Email: "hello@foo.bar",
UUID: "9c31b040-59e2-4a2b-926b-334d9e3fbd05",
}); err != nil {
t.Error(err)
}
if err := c.Invalidate("accounts", "hello@foo.bar"); err != nil {
t.Error(err)
}
v, err := c.Get("accounts", "hello@foo.bar")
if err != nil {
t.Error(err)
}
if v.Valid {
t.Errorf("cache key unexpected valid state")
}
c.Evict()
if c.Length("accounts") != 0 {
t.Errorf("expected length 0 got `%v`", len(c.entries))
}
}
func TestGet(t *testing.T) {
svcCache := NewCache(
Size(256),
)
err := svcCache.Set("accounts", "node", "0.0.0.0:1234")
if err != nil {
t.Error(err)
}
raw, err := svcCache.Get("accounts", "node")
if err != nil {
t.Error(err)
}
v, ok := raw.V.(string)
if !ok {
t.Errorf("invalid type on service node key")
}
if v != "0.0.0.0:1234" {
t.Errorf("expected `0.0.0.0:1234` got `%v`", v)
}
}

36
proxy/pkg/cache/option.go vendored Normal file
View File

@@ -0,0 +1,36 @@
package cache
import "time"
// Options are all the possible options.
type Options struct {
size int
ttl time.Duration
}
// Option mutates option
type Option func(*Options)
// Size configures the size of the cache in items.
func Size(s int) Option {
return func(o *Options) {
o.size = s
}
}
// TTL rebuilds the cache after the configured duration.
func TTL(ttl time.Duration) Option {
return func(o *Options) {
o.ttl = ttl
}
}
func newOptions(opts ...Option) Options {
o := Options{}
for _, v := range opts {
v(&o)
}
return o
}

View File

@@ -0,0 +1,49 @@
package command
import (
"fmt"
"net/http"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis-proxy/pkg/config"
"github.com/owncloud/ocis-proxy/pkg/flagset"
)
// Health is the entrypoint for the health command.
func Health(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "health",
Usage: "Check health status",
Flags: flagset.HealthWithConfig(cfg),
Action: func(c *cli.Context) error {
logger := NewLogger(cfg)
resp, err := http.Get(
fmt.Sprintf(
"http://%s/healthz",
cfg.Debug.Addr,
),
)
if err != nil {
logger.Fatal().
Err(err).
Msg("Failed to request health check")
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
logger.Fatal().
Int("code", resp.StatusCode).
Msg("Health seems to be in bad state")
}
logger.Debug().
Int("code", resp.StatusCode).
Msg("Health got a good state")
return nil
},
}
}

108
proxy/pkg/command/root.go Normal file
View File

@@ -0,0 +1,108 @@
package command
import (
"os"
"strings"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-proxy/pkg/config"
"github.com/owncloud/ocis-proxy/pkg/flagset"
"github.com/owncloud/ocis-proxy/pkg/version"
"github.com/spf13/viper"
)
// Execute is the entry point for the ocis-proxy command.
func Execute() error {
cfg := config.New()
app := &cli.App{
Name: "ocis-proxy",
Version: version.String,
Usage: "proxy for Reva/oCIS",
Compiled: version.Compiled(),
Authors: []*cli.Author{
{
Name: "ownCloud GmbH",
Email: "support@owncloud.com",
},
},
Flags: flagset.RootWithConfig(cfg),
Before: func(c *cli.Context) error {
return ParseConfig(c, cfg)
},
Commands: []*cli.Command{
Server(cfg),
Health(cfg),
},
}
cli.HelpFlag = &cli.BoolFlag{
Name: "help,h",
Usage: "Show the help",
}
cli.VersionFlag = &cli.BoolFlag{
Name: "version,v",
Usage: "Print the version",
}
return app.Run(os.Args)
}
// NewLogger initializes a service-specific logger instance.
func NewLogger(cfg *config.Config) log.Logger {
return log.NewLogger(
log.Name("proxy"),
log.Level(cfg.Log.Level),
log.Pretty(cfg.Log.Pretty),
log.Color(cfg.Log.Color),
)
}
// ParseConfig loads proxy configuration from Viper known paths.
func ParseConfig(c *cli.Context, cfg *config.Config) error {
logger := NewLogger(cfg)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetEnvPrefix("PROXY")
viper.AutomaticEnv()
if c.IsSet("config-file") {
viper.SetConfigFile(c.String("config-file"))
} else {
viper.SetConfigName("proxy")
viper.AddConfigPath("/etc/ocis")
viper.AddConfigPath("$HOME/.ocis")
viper.AddConfigPath("./config")
}
if err := viper.ReadInConfig(); err != nil {
switch err.(type) {
case viper.ConfigFileNotFoundError:
logger.Info().
Msg("Continue without config")
case viper.UnsupportedConfigError:
logger.Fatal().
Err(err).
Msg("Unsupported config type")
default:
logger.Fatal().
Err(err).
Msg("Failed to read config")
}
}
if err := viper.Unmarshal(&cfg); err != nil {
logger.Fatal().
Err(err).
Msg("Failed to parse config")
}
return nil
}

319
proxy/pkg/command/server.go Normal file
View File

@@ -0,0 +1,319 @@
package command
import (
"context"
"crypto/tls"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"time"
"contrib.go.opencensus.io/exporter/jaeger"
"contrib.go.opencensus.io/exporter/ocagent"
"contrib.go.opencensus.io/exporter/zipkin"
"github.com/coreos/go-oidc"
"github.com/justinas/alice"
"github.com/micro/cli/v2"
mclient "github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/client/grpc"
"github.com/oklog/run"
openzipkin "github.com/openzipkin/zipkin-go"
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
acc "github.com/owncloud/ocis-accounts/pkg/proto/v0"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-proxy/pkg/config"
"github.com/owncloud/ocis-proxy/pkg/cs3"
"github.com/owncloud/ocis-proxy/pkg/flagset"
"github.com/owncloud/ocis-proxy/pkg/metrics"
"github.com/owncloud/ocis-proxy/pkg/middleware"
"github.com/owncloud/ocis-proxy/pkg/proxy"
"github.com/owncloud/ocis-proxy/pkg/server/debug"
proxyHTTP "github.com/owncloud/ocis-proxy/pkg/server/http"
settings "github.com/owncloud/ocis-settings/pkg/proto/v0"
storepb "github.com/owncloud/ocis-store/pkg/proto/v0"
"go.opencensus.io/stats/view"
"go.opencensus.io/trace"
"golang.org/x/oauth2"
)
// Server is the entrypoint for the server command.
func Server(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "server",
Usage: "Start integrated server",
Flags: flagset.ServerWithConfig(cfg),
Before: func(ctx *cli.Context) error {
l := NewLogger(cfg)
l.Debug().Str("tracing", strconv.FormatBool(cfg.Tracing.Enabled)).Msg("init: before")
if cfg.HTTP.Root != "/" {
cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/")
}
cfg.PreSignedURL.AllowedHTTPMethods = ctx.StringSlice("presignedurl-allow-method")
// When running on single binary mode the before hook from the root command won't get called. We manually
// call this before hook from ocis command, so the configuration can be loaded.
return ParseConfig(ctx, cfg)
},
Action: func(c *cli.Context) error {
logger := NewLogger(cfg)
httpNamespace := c.String("http-namespace")
if cfg.Tracing.Enabled {
switch t := cfg.Tracing.Type; t {
case "agent":
exporter, err := ocagent.NewExporter(
ocagent.WithReconnectionPeriod(5*time.Second),
ocagent.WithAddress(cfg.Tracing.Endpoint),
ocagent.WithServiceName(cfg.Tracing.Service),
)
if err != nil {
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create agent tracing")
return err
}
trace.RegisterExporter(exporter)
view.RegisterExporter(exporter)
case "jaeger":
exporter, err := jaeger.NewExporter(
jaeger.Options{
AgentEndpoint: cfg.Tracing.Endpoint,
CollectorEndpoint: cfg.Tracing.Collector,
ServiceName: cfg.Tracing.Service,
},
)
if err != nil {
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create jaeger tracing")
return err
}
trace.RegisterExporter(exporter)
case "zipkin":
endpoint, err := openzipkin.NewEndpoint(
cfg.Tracing.Service,
cfg.Tracing.Endpoint,
)
if err != nil {
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create zipkin tracing")
return err
}
exporter := zipkin.NewExporter(
zipkinhttp.NewReporter(
cfg.Tracing.Collector,
),
endpoint,
)
trace.RegisterExporter(exporter)
default:
logger.Warn().
Str("type", t).
Msg("Unknown tracing backend")
}
trace.ApplyConfig(
trace.Config{
DefaultSampler: trace.AlwaysSample(),
},
)
} else {
logger.Debug().
Msg("Tracing is not enabled")
}
var (
gr = run.Group{}
ctx, cancel = context.WithCancel(context.Background())
metrics = metrics.New()
)
defer cancel()
rp := proxy.NewMultiHostReverseProxy(
proxy.Logger(logger),
proxy.Config(cfg),
)
{
server, err := proxyHTTP.Server(
proxyHTTP.Handler(rp),
proxyHTTP.Logger(logger),
proxyHTTP.Namespace(httpNamespace),
proxyHTTP.Context(ctx),
proxyHTTP.Config(cfg),
proxyHTTP.Metrics(metrics),
proxyHTTP.Flags(flagset.RootWithConfig(config.New())),
proxyHTTP.Flags(flagset.ServerWithConfig(config.New())),
proxyHTTP.Middlewares(loadMiddlewares(ctx, logger, cfg)),
)
if err != nil {
logger.Error().
Err(err).
Str("server", "http").
Msg("Failed to initialize server")
return err
}
gr.Add(func() error {
return server.Run()
}, func(_ error) {
logger.Info().
Str("server", "http").
Msg("Shutting down server")
cancel()
})
}
{
server, err := debug.Server(
debug.Logger(logger),
debug.Context(ctx),
debug.Config(cfg),
)
if err != nil {
logger.Error().
Err(err).
Str("server", "debug").
Msg("Failed to initialize server")
return err
}
gr.Add(func() error {
return server.ListenAndServe()
}, func(_ error) {
ctx, timeout := context.WithTimeout(ctx, 5*time.Second)
defer timeout()
defer cancel()
if err := server.Shutdown(ctx); err != nil {
logger.Error().
Err(err).
Str("server", "debug").
Msg("Failed to shutdown server")
} else {
logger.Info().
Str("server", "debug").
Msg("Shutting down server")
}
})
}
{
stop := make(chan os.Signal, 1)
gr.Add(func() error {
signal.Notify(stop, os.Interrupt)
<-stop
return nil
}, func(err error) {
close(stop)
cancel()
})
}
return gr.Run()
},
}
}
func loadMiddlewares(ctx context.Context, l log.Logger, cfg *config.Config) alice.Chain {
psMW := middleware.PresignedURL(
middleware.Logger(l),
middleware.Store(storepb.NewStoreService("com.owncloud.api.store", grpc.NewClient())),
middleware.PreSignedURLConfig(cfg.PreSignedURL),
)
// TODO this won't work with a registry other than mdns. Look into Micro's client initialization.
// https://github.com/owncloud/ocis-proxy/issues/38
accounts := acc.NewAccountsService("com.owncloud.api.accounts", mclient.DefaultClient)
roles := settings.NewRoleService("com.owncloud.api.settings", mclient.DefaultClient)
uuidMW := middleware.AccountUUID(
middleware.Logger(l),
middleware.TokenManagerConfig(cfg.TokenManager),
middleware.AccountsClient(accounts),
middleware.SettingsRoleService(roles),
)
// the connection will be established in a non blocking fashion
sc, err := cs3.GetGatewayServiceClient(cfg.Reva.Address)
if err != nil {
l.Error().Err(err).
Str("gateway", cfg.Reva.Address).
Msg("Failed to create reva gateway service client")
}
chMW := middleware.CreateHome(
middleware.Logger(l),
middleware.RevaGatewayClient(sc),
middleware.AccountsClient(accounts),
middleware.TokenManagerConfig(cfg.TokenManager),
)
if cfg.OIDC.Issuer != "" {
l.Info().Msg("Loading OIDC-Middleware")
l.Debug().Interface("oidc_config", cfg.OIDC).Msg("OIDC-Config")
var oidcHTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: cfg.OIDC.Insecure,
},
DisableKeepAlives: true,
},
Timeout: time.Second * 10,
}
customCtx := context.WithValue(ctx, oauth2.HTTPClient, oidcHTTPClient)
// Initialize a provider by specifying the issuer URL.
// it will fetch the keys from the issuer using the .well-known
// endpoint
provider := func() (middleware.OIDCProvider, error) {
return oidc.NewProvider(customCtx, cfg.OIDC.Issuer)
}
oidcMW := middleware.OpenIDConnect(
middleware.Logger(l),
middleware.HTTPClient(oidcHTTPClient),
middleware.OIDCProviderFunc(provider),
middleware.OIDCIss(cfg.OIDC.Issuer),
)
return alice.New(middleware.RedirectToHTTPS, oidcMW, psMW, uuidMW, chMW)
}
return alice.New(middleware.RedirectToHTTPS, psMW, uuidMW, chMW)
}

134
proxy/pkg/config/config.go Normal file
View File

@@ -0,0 +1,134 @@
package config
// Log defines the available logging configuration.
type Log struct {
Level string
Pretty bool
Color bool
}
// Debug defines the available debug configuration.
type Debug struct {
Addr string
Token string
Pprof bool
Zpages bool
}
// HTTP defines the available http configuration.
type HTTP struct {
Addr string
Namespace string
Root string
TLSCert string
TLSKey string
TLS bool
}
// Tracing defines the available tracing configuration.
type Tracing struct {
Enabled bool
Type string
Endpoint string
Collector string
Service string
}
// Asset defines the available asset configuration.
type Asset struct {
Path string
}
// Policy enables us to use multiple directors.
type Policy struct {
Name string
Routes []Route
}
// Route define forwarding routes
type Route struct {
Type RouteType
Endpoint string
Backend string
ApacheVHost bool `mapstructure:"apache-vhost"`
}
// RouteType defines the type of a route
type RouteType string
const (
// PrefixRoute are routes matched by a prefix
PrefixRoute RouteType = "prefix"
// QueryRoute are routes machted by a prefix and query parameters
QueryRoute RouteType = "query"
// RegexRoute are routes matched by a pattern
RegexRoute RouteType = "regex"
// DefaultRouteType is the PrefixRoute
DefaultRouteType RouteType = PrefixRoute
)
var (
// RouteTypes is an array of the available route types
RouteTypes []RouteType = []RouteType{QueryRoute, RegexRoute, PrefixRoute}
)
// Reva defines all available REVA configuration.
type Reva struct {
Address string
}
// Config combines all available configuration parts.
type Config struct {
File string
Log Log
Debug Debug
HTTP HTTP
Tracing Tracing
Asset Asset
Policies []Policy
OIDC OIDC
TokenManager TokenManager
PolicySelector *PolicySelector `mapstructure:"policy_selector"`
Reva Reva
PreSignedURL PreSignedURL
}
// OIDC is the config for the OpenID-Connect middleware. If set the proxy will try to authenticate every request
// with the configured oidc-provider
type OIDC struct {
Issuer string
Insecure bool
}
// PolicySelector is the toplevel-configuration for different selectors
type PolicySelector struct {
Static *StaticSelectorConf
Migration *MigrationSelectorConf
}
// StaticSelectorConf is the config for the static-policy-selector
type StaticSelectorConf struct {
Policy string
}
// TokenManager is the config for using the reva token manager
type TokenManager struct {
JWTSecret string
}
// PreSignedURL is the config for the presigned url middleware
type PreSignedURL struct {
AllowedHTTPMethods []string
}
// MigrationSelectorConf is the config for the migration-selector
type MigrationSelectorConf struct {
AccFoundPolicy string `mapstructure:"acc_found_policy"`
AccNotFoundPolicy string `mapstructure:"acc_not_found_policy"`
UnauthenticatedPolicy string `mapstructure:"unauthenticated_policy"`
}
// New initializes a new configuration
func New() *Config {
return &Config{}
}

123
proxy/pkg/crypto/gencert.go Normal file
View File

@@ -0,0 +1,123 @@
package crypto
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"time"
"github.com/owncloud/ocis-pkg/v2/log"
)
func publicKey(priv interface{}) interface{} {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
default:
return nil
}
}
func pemBlockForKey(priv interface{}, l log.Logger) *pem.Block {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
l.Fatal().Err(err).Msg("Unable to marshal ECDSA private key")
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
return nil
}
}
// GenCert generates TLS-Certificates
func GenCert(l log.Logger) error {
var priv interface{}
var err error
priv, err = rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
l.Fatal().Err(err).Msg("Failed to generate private key")
}
notBefore := time.Now()
notAfter := notBefore.Add(24 * time.Hour * 365)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
l.Fatal().Err(err).Msg("Failed to generate serial number")
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Acme Corp"},
CommonName: "OCIS",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
hosts := []string{"127.0.0.1", "localhost"}
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
//template.IsCA = true
//template.KeyUsage |= x509.KeyUsageCertSign
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil {
l.Fatal().Err(err).Msg("Failed to create certificate")
}
certOut, err := os.Create("server.crt")
if err != nil {
l.Fatal().Err(err).Msg("Failed to open server.crt for writing")
}
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil {
l.Fatal().Err(err).Msg("Failed to encode certificate")
}
err = certOut.Close()
if err != nil {
l.Fatal().Err(err).Msg("Failed to write cert")
}
l.Info().Msg("Written server.crt")
keyOut, err := os.OpenFile("server.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
l.Fatal().Err(err).Msg("Failed to open server.key for writing")
}
err = pem.Encode(keyOut, pemBlockForKey(priv, l))
if err != nil {
l.Fatal().Err(err).Msg("Failed to encode key")
}
err = keyOut.Close()
if err != nil {
l.Fatal().Err(err).Msg("Failed to write key")
}
l.Info().Msg("Written server.key")
return nil
}

26
proxy/pkg/cs3/client.go Normal file
View File

@@ -0,0 +1,26 @@
package cs3
import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"google.golang.org/grpc"
)
func newConn(endpoint string) (*grpc.ClientConn, error) {
conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
if err != nil {
return nil, err
}
return conn, nil
}
// GetGatewayServiceClient returns a new cs3 gateway client
func GetGatewayServiceClient(endpoint string) (gateway.GatewayAPIClient, error) {
conn, err := newConn(endpoint)
if err != nil {
return nil, err
}
return gateway.NewGatewayAPIClient(conn), nil
}

View File

@@ -0,0 +1,206 @@
package flagset
import (
"github.com/micro/cli/v2"
"github.com/owncloud/ocis-proxy/pkg/config"
)
// RootWithConfig applies cfg to the root flagset
func RootWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "config-file",
Value: "",
Usage: "Path to config file",
EnvVars: []string{"PROXY_CONFIG_FILE"},
Destination: &cfg.File,
},
&cli.StringFlag{
Name: "log-level",
Value: "info",
Usage: "Set logging level",
EnvVars: []string{"PROXY_LOG_LEVEL"},
Destination: &cfg.Log.Level,
},
&cli.BoolFlag{
Name: "log-pretty",
Value: true,
Usage: "Enable pretty logging",
EnvVars: []string{"PROXY_LOG_PRETTY"},
Destination: &cfg.Log.Pretty,
},
&cli.BoolFlag{
Name: "log-color",
Value: true,
Usage: "Enable colored logging",
EnvVars: []string{"PROXY_LOG_COLOR"},
Destination: &cfg.Log.Color,
},
}
}
// HealthWithConfig applies cfg to the root flagset
func HealthWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "debug-addr",
Value: "0.0.0.0:9109",
Usage: "Address to debug endpoint",
EnvVars: []string{"PROXY_DEBUG_ADDR"},
Destination: &cfg.Debug.Addr,
},
}
}
// ServerWithConfig applies cfg to the root flagset
func ServerWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
Name: "tracing-enabled",
Usage: "Enable sending traces",
EnvVars: []string{"PROXY_TRACING_ENABLED"},
Destination: &cfg.Tracing.Enabled,
},
&cli.StringFlag{
Name: "tracing-type",
Value: "jaeger",
Usage: "Tracing backend type",
EnvVars: []string{"PROXY_TRACING_TYPE"},
Destination: &cfg.Tracing.Type,
},
&cli.StringFlag{
Name: "tracing-endpoint",
Value: "",
Usage: "Endpoint for the agent",
EnvVars: []string{"PROXY_TRACING_ENDPOINT"},
Destination: &cfg.Tracing.Endpoint,
},
&cli.StringFlag{
Name: "tracing-collector",
Value: "http://localhost:14268/api/traces",
Usage: "Endpoint for the collector",
EnvVars: []string{"PROXY_TRACING_COLLECTOR"},
Destination: &cfg.Tracing.Collector,
},
&cli.StringFlag{
Name: "tracing-service",
Value: "proxy",
Usage: "Service name for tracing",
EnvVars: []string{"PROXY_TRACING_SERVICE"},
Destination: &cfg.Tracing.Service,
},
&cli.StringFlag{
Name: "debug-addr",
Value: "0.0.0.0:9205",
Usage: "Address to bind debug server",
EnvVars: []string{"PROXY_DEBUG_ADDR"},
Destination: &cfg.Debug.Addr,
},
&cli.StringFlag{
Name: "debug-token",
Value: "",
Usage: "Token to grant metrics access",
EnvVars: []string{"PROXY_DEBUG_TOKEN"},
Destination: &cfg.Debug.Token,
},
&cli.BoolFlag{
Name: "debug-pprof",
Usage: "Enable pprof debugging",
EnvVars: []string{"PROXY_DEBUG_PPROF"},
Destination: &cfg.Debug.Pprof,
},
&cli.BoolFlag{
Name: "debug-zpages",
Usage: "Enable zpages debugging",
EnvVars: []string{"PROXY_DEBUG_ZPAGES"},
Destination: &cfg.Debug.Zpages,
},
&cli.StringFlag{
Name: "http-addr",
Value: "0.0.0.0:9200",
Usage: "Address to bind http server",
EnvVars: []string{"PROXY_HTTP_ADDR"},
Destination: &cfg.HTTP.Addr,
},
&cli.StringFlag{
Name: "http-root",
Value: "/",
Usage: "Root path of http server",
EnvVars: []string{"PROXY_HTTP_ROOT"},
Destination: &cfg.HTTP.Root,
},
&cli.StringFlag{
Name: "asset-path",
Value: "",
Usage: "Path to custom assets",
EnvVars: []string{"PROXY_ASSET_PATH"},
Destination: &cfg.Asset.Path,
},
&cli.StringFlag{
Name: "http-namespace",
Value: "com.owncloud",
Usage: "Set the base namespace for the http namespace",
EnvVars: []string{"PROXY_HTTP_NAMESPACE"},
Destination: &cfg.HTTP.Namespace,
},
&cli.StringFlag{
Name: "transport-tls-cert",
Value: "",
Usage: "Certificate file for transport encryption",
EnvVars: []string{"PROXY_TRANSPORT_TLS_CERT"},
Destination: &cfg.HTTP.TLSCert,
},
&cli.StringFlag{
Name: "transport-tls-key",
Value: "",
Usage: "Secret file for transport encryption",
EnvVars: []string{"PROXY_TRANSPORT_TLS_KEY"},
Destination: &cfg.HTTP.TLSKey,
},
&cli.BoolFlag{
Name: "tls",
Usage: "Use TLS (disable only if proxy is behind a TLS-terminating reverse-proxy).",
EnvVars: []string{"PROXY_TLS"},
Value: true,
Destination: &cfg.HTTP.TLS,
},
&cli.StringFlag{
Name: "jwt-secret",
Value: "Pive-Fumkiu4",
Usage: "Used to create JWT to talk to reva, should equal reva's jwt-secret",
EnvVars: []string{"PROXY_JWT_SECRET"},
Destination: &cfg.TokenManager.JWTSecret,
},
&cli.StringFlag{
Name: "reva-gateway-addr",
Value: "127.0.0.1:9142",
Usage: "REVA Gateway Endpoint",
EnvVars: []string{"PROXY_REVA_GATEWAY_ADDR"},
Destination: &cfg.Reva.Address,
},
// OIDC
&cli.StringFlag{
Name: "oidc-issuer",
Value: "https://localhost:9200",
Usage: "OIDC issuer",
EnvVars: []string{"PROXY_OIDC_ISSUER"},
Destination: &cfg.OIDC.Issuer,
},
&cli.BoolFlag{
Name: "oidc-insecure",
Value: true,
Usage: "OIDC allow insecure communication",
EnvVars: []string{"PROXY_OIDC_INSECURE"},
Destination: &cfg.OIDC.Insecure,
},
&cli.StringSliceFlag{
Name: "presignedurl-allow-method",
Value: cli.NewStringSlice("GET"),
Usage: "--presignedurl-allow-method GET [--presignedurl-allow-method POST]",
EnvVars: []string{"PRESIGNEDURL_ALLOWED_METHODS"},
},
}
}

View File

@@ -0,0 +1,58 @@
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
// Namespace defines the namespace for the defines metrics.
Namespace = "ocis"
// Subsystem defines the subsystem for the defines metrics.
Subsystem = "proxy"
)
// Metrics defines the available metrics of this service.
type Metrics struct {
Counter *prometheus.CounterVec
Latency *prometheus.SummaryVec
Duration *prometheus.HistogramVec
}
// New initializes the available metrics.
func New() *Metrics {
m := &Metrics{
Counter: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: Namespace,
Subsystem: Subsystem,
Name: "proxy_total",
Help: "How many proxy requests processed",
}, []string{}),
Latency: prometheus.NewSummaryVec(prometheus.SummaryOpts{
Namespace: Namespace,
Subsystem: Subsystem,
Name: "proxy_latency_microseconds",
Help: "proxy request latencies in microseconds",
}, []string{}),
Duration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: Namespace,
Subsystem: Subsystem,
Name: "proxy_duration_seconds",
Help: "proxy method request time in seconds",
}, []string{}),
}
prometheus.Register(
m.Counter,
)
prometheus.Register(
m.Latency,
)
prometheus.Register(
m.Duration,
)
return m
}

View File

@@ -0,0 +1,189 @@
package middleware
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
revauser "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/token/manager/jwt"
acc "github.com/owncloud/ocis-accounts/pkg/proto/v0"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-pkg/v2/oidc"
settings "github.com/owncloud/ocis-settings/pkg/proto/v0"
)
func getAccount(l log.Logger, ac acc.AccountsService, query string) (account *acc.Account, status int) {
resp, err := ac.ListAccounts(context.Background(), &acc.ListAccountsRequest{
Query: query,
PageSize: 2,
})
if err != nil {
l.Error().Err(err).Str("query", query).Msgf("Error fetching from accounts-service")
status = http.StatusInternalServerError
return
}
if len(resp.Accounts) <= 0 {
l.Error().Str("query", query).Msgf("Account not found")
status = http.StatusNotFound
return
}
if len(resp.Accounts) > 1 {
l.Error().Str("query", query).Msgf("More than one account found. Not logging user in.")
status = http.StatusForbidden
return
}
account = resp.Accounts[0]
return
}
func createAccount(l log.Logger, claims *oidc.StandardClaims, ac acc.AccountsService) (*acc.Account, int) {
// TODO check if fields are missing.
req := &acc.CreateAccountRequest{
Account: &acc.Account{
DisplayName: claims.DisplayName,
PreferredName: claims.PreferredUsername,
OnPremisesSamAccountName: claims.PreferredUsername,
Mail: claims.Email,
CreationType: "LocalAccount",
AccountEnabled: true,
// TODO assign uidnumber and gidnumber? better do that in ocis-accounts as it can keep track of the next numbers
},
}
created, err := ac.CreateAccount(context.Background(), req)
if err != nil {
l.Error().Err(err).Interface("account", req.Account).Msg("could not create account")
return nil, http.StatusInternalServerError
}
return created, 0
}
// AccountUUID provides a middleware which mints a jwt and adds it to the proxied request based
// on the oidc-claims
func AccountUUID(opts ...Option) func(next http.Handler) http.Handler {
opt := newOptions(opts...)
return func(next http.Handler) http.Handler {
// TODO: handle error
tokenManager, err := jwt.New(map[string]interface{}{
"secret": opt.TokenManagerConfig.JWTSecret,
"expires": int64(60),
})
if err != nil {
opt.Logger.Fatal().Err(err).Msgf("Could not initialize token-manager")
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l := opt.Logger
claims := oidc.FromContext(r.Context())
if claims == nil {
next.ServeHTTP(w, r)
return
}
var account *acc.Account
var status int
if claims.Email != "" {
account, status = getAccount(l, opt.AccountsClient, fmt.Sprintf("mail eq '%s'", strings.ReplaceAll(claims.Email, "'", "''")))
} else if claims.PreferredUsername != "" {
account, status = getAccount(l, opt.AccountsClient, fmt.Sprintf("preferred_name eq '%s'", strings.ReplaceAll(claims.PreferredUsername, "'", "''")))
} else if claims.OcisID != "" {
account, status = getAccount(l, opt.AccountsClient, fmt.Sprintf("id eq '%s'", strings.ReplaceAll(claims.OcisID, "'", "''")))
} else {
// TODO allow lookup by custom claim, eg an id ... or sub
l.Error().Err(err).Msgf("Could not lookup account, no mail or preferred_username claim set")
w.WriteHeader(http.StatusInternalServerError)
}
if status != 0 || account == nil {
if status == http.StatusNotFound {
account, status = createAccount(l, claims, opt.AccountsClient)
if status != 0 {
w.WriteHeader(status)
return
}
} else {
w.WriteHeader(status)
return
}
}
if !account.AccountEnabled {
l.Debug().Interface("account", account).Msg("account is disabled")
w.WriteHeader(http.StatusUnauthorized)
return
}
groups := make([]string, len(account.MemberOf))
for i := range account.MemberOf {
// reva needs the unix group name
groups[i] = account.MemberOf[i].OnPremisesSamAccountName
}
// fetch active roles from ocis-settings
assignmentResponse, err := opt.SettingsRoleService.ListRoleAssignments(r.Context(), &settings.ListRoleAssignmentsRequest{AccountUuid: account.Id})
roleIDs := make([]string, 0)
if err != nil {
l.Err(err).Str("accountID", account.Id).Msg("failed to fetch role assignments")
} else {
for _, assignment := range assignmentResponse.Assignments {
roleIDs = append(roleIDs, assignment.RoleId)
}
}
l.Debug().Interface("claims", claims).Interface("account", account).Msgf("Associated claims with uuid")
user := &revauser.User{
Id: &revauser.UserId{
OpaqueId: account.Id,
Idp: claims.Iss,
},
Username: account.OnPremisesSamAccountName,
DisplayName: account.DisplayName,
Mail: account.Mail,
MailVerified: account.ExternalUserState == "" || account.ExternalUserState == "Accepted",
Groups: groups,
Opaque: &types.Opaque{
Map: map[string]*types.OpaqueEntry{},
},
}
user.Opaque.Map["uid"] = &types.OpaqueEntry{
Decoder: "plain",
Value: []byte(strconv.FormatInt(account.UidNumber, 10)),
}
user.Opaque.Map["gid"] = &types.OpaqueEntry{
Decoder: "plain",
Value: []byte(strconv.FormatInt(account.GidNumber, 10)),
}
// encode roleIDs as json string
roleIDsJSON, jsonErr := json.Marshal(roleIDs)
if jsonErr != nil {
l.Err(jsonErr).Str("accountID", account.Id).Msg("failed to marshal roleIDs into json")
} else {
user.Opaque.Map["roles"] = &types.OpaqueEntry{
Decoder: "json",
Value: roleIDsJSON,
}
}
token, err := tokenManager.MintToken(r.Context(), user)
if err != nil {
l.Error().Err(err).Msgf("Could not mint token")
w.WriteHeader(http.StatusInternalServerError)
return
}
r.Header.Set("x-access-token", token)
next.ServeHTTP(w, r)
})
}
}

View File

@@ -0,0 +1,106 @@
package middleware
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/micro/go-micro/v2/client"
"github.com/owncloud/ocis-accounts/pkg/proto/v0"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-pkg/v2/oidc"
"github.com/owncloud/ocis-proxy/pkg/config"
settings "github.com/owncloud/ocis-settings/pkg/proto/v0"
)
// TODO testing the getAccount method should inject a cache
func TestGetAccountSuccess(t *testing.T) {
svcCache.Invalidate(AccountsKey, "success")
if _, status := getAccount(log.NewLogger(), mockAccountUUIDMiddlewareAccSvc(false, true), "mail eq 'success'"); status != 0 {
t.Errorf("expected an account")
}
}
func TestGetAccountInternalError(t *testing.T) {
svcCache.Invalidate(AccountsKey, "failure")
if _, status := getAccount(log.NewLogger(), mockAccountUUIDMiddlewareAccSvc(true, false), "mail eq 'failure'"); status != http.StatusInternalServerError {
t.Errorf("expected an internal server error")
}
}
func TestAccountUUIDMiddleware(t *testing.T) {
svcCache.Invalidate(AccountsKey, "success")
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
m := AccountUUID(
Logger(log.NewLogger()),
TokenManagerConfig(config.TokenManager{JWTSecret: "secret"}),
AccountsClient(mockAccountUUIDMiddlewareAccSvc(false, true)),
SettingsRoleService(mockAccountUUIDMiddlewareRolesSvc(false)),
)(next)
r := httptest.NewRequest(http.MethodGet, "http://www.example.com", nil)
w := httptest.NewRecorder()
ctx := oidc.NewContext(r.Context(), &oidc.StandardClaims{Email: "success"})
r = r.WithContext(ctx)
m.ServeHTTP(w, r)
if r.Header.Get("x-access-token") == "" {
t.Errorf("expected a token")
}
}
func TestAccountUUIDMiddlewareWithDisabledAccount(t *testing.T) {
svcCache.Invalidate(AccountsKey, "failure")
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
m := AccountUUID(
Logger(log.NewLogger()),
TokenManagerConfig(config.TokenManager{JWTSecret: "secret"}),
AccountsClient(mockAccountUUIDMiddlewareAccSvc(false, false)),
SettingsRoleService(mockAccountUUIDMiddlewareRolesSvc(false)),
)(next)
r := httptest.NewRequest(http.MethodGet, "http://www.example.com", nil)
w := httptest.NewRecorder()
ctx := oidc.NewContext(r.Context(), &oidc.StandardClaims{Email: "failure"})
r = r.WithContext(ctx)
m.ServeHTTP(w, r)
rsp := w.Result()
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusUnauthorized {
t.Errorf("expected a disabled account to be unauthorized, got: %d", rsp.StatusCode)
}
}
func mockAccountUUIDMiddlewareAccSvc(retErr, accEnabled bool) proto.AccountsService {
return &proto.MockAccountsService{
ListFunc: func(ctx context.Context, in *proto.ListAccountsRequest, opts ...client.CallOption) (out *proto.ListAccountsResponse, err error) {
if retErr {
return nil, fmt.Errorf("error returned by mockAccountsService LIST")
}
return &proto.ListAccountsResponse{
Accounts: []*proto.Account{
{
Id: "yay",
AccountEnabled: accEnabled,
},
},
}, nil
},
}
}
func mockAccountUUIDMiddlewareRolesSvc(returnError bool) settings.RoleService {
return &settings.MockRoleService{
ListRoleAssignmentsFunc: func(ctx context.Context, req *settings.ListRoleAssignmentsRequest, opts ...client.CallOption) (res *settings.ListRoleAssignmentsResponse, err error) {
if returnError {
return nil, fmt.Errorf("error returned by mockRoleService.ListRoleAssignments")
}
return &settings.ListRoleAssignmentsResponse{
Assignments: []*settings.UserRoleAssignment{},
}, nil
},
}
}

View File

@@ -0,0 +1,77 @@
package middleware
import (
"net/http"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/rgrpc/status"
tokenpkg "github.com/cs3org/reva/pkg/token"
"github.com/cs3org/reva/pkg/token/manager/jwt"
"github.com/micro/go-micro/v2/errors"
"github.com/owncloud/ocis-accounts/pkg/proto/v0"
"google.golang.org/grpc/metadata"
)
// CreateHome provides a middleware which sends a CreateHome request to the reva gateway
func CreateHome(opts ...Option) func(next http.Handler) http.Handler {
opt := newOptions(opts...)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
accounts := opt.AccountsClient
tokenManager, err := jwt.New(map[string]interface{}{
"secret": opt.TokenManagerConfig.JWTSecret,
})
if err != nil {
opt.Logger.Error().Err(err).Msg("error creating a token manager")
w.WriteHeader(http.StatusInternalServerError)
return
}
token := r.Header.Get("x-access-token")
if token == "" {
next.ServeHTTP(w, r)
return
}
user, err := tokenManager.DismantleToken(r.Context(), token)
if err != nil {
opt.Logger.Err(err).Msg("error getting user from access token")
w.WriteHeader(http.StatusInternalServerError)
return
}
_, err = accounts.GetAccount(r.Context(), &proto.GetAccountRequest{
Id: user.Id.OpaqueId,
})
if err != nil {
e := errors.Parse(err.Error())
if e.Code == http.StatusNotFound {
opt.Logger.Debug().Msgf("account with id %s not found", user.Id.OpaqueId)
next.ServeHTTP(w, r)
return
}
opt.Logger.Err(err).Msgf("error getting user with id %s from accounts service", user.Id.OpaqueId)
w.WriteHeader(http.StatusInternalServerError)
return
}
// we need to pass the token to authenticate the CreateHome request.
//ctx := tokenpkg.ContextSetToken(r.Context(), token)
ctx := metadata.AppendToOutgoingContext(r.Context(), tokenpkg.TokenHeader, token)
createHomeReq := &provider.CreateHomeRequest{}
createHomeRes, err := opt.RevaGatewayClient.CreateHome(ctx, createHomeReq)
if err != nil {
opt.Logger.Err(err).Msg("error calling CreateHome")
} else if createHomeRes.Status.Code != rpc.Code_CODE_OK {
err := status.NewErrorFromCode(createHomeRes.Status.Code, "gateway")
opt.Logger.Err(err).Msg("error when calling Createhome")
}
next.ServeHTTP(w, r)
})
}
}

View File

@@ -0,0 +1,19 @@
package middleware
import (
"fmt"
"net/http"
)
// RedirectToHTTPS redirects insecure requests to https
func RedirectToHTTPS(next http.Handler) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
proto := req.Header.Get("x-forwarded-proto")
if proto == "http" || proto == "HTTP" {
http.Redirect(res, req, fmt.Sprintf("https://%s%s", req.Host, req.URL), http.StatusPermanentRedirect)
return
}
next.ServeHTTP(res, req)
})
}

View File

@@ -0,0 +1,6 @@
package middleware
import "net/http"
// M undocummented
type M func(next http.Handler) http.Handler

View File

@@ -0,0 +1,119 @@
package middleware
import (
"context"
"errors"
"net/http"
"strings"
"github.com/coreos/go-oidc"
ocisoidc "github.com/owncloud/ocis-pkg/v2/oidc"
"github.com/owncloud/ocis-proxy/pkg/cache"
"golang.org/x/oauth2"
)
var (
// ErrInvalidToken is returned when the request token is invalid.
ErrInvalidToken = errors.New("invalid or missing token")
// svcCache caches requests for given services to prevent round trips to the service
svcCache = cache.NewCache(
cache.Size(256),
)
)
// OIDCProvider used to mock the oidc provider during tests
type OIDCProvider interface {
UserInfo(ctx context.Context, ts oauth2.TokenSource) (*oidc.UserInfo, error)
}
// OpenIDConnect provides a middleware to check access secured by a static token.
func OpenIDConnect(opts ...Option) func(next http.Handler) http.Handler {
opt := newOptions(opts...)
return func(next http.Handler) http.Handler {
var oidcProvider OIDCProvider
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
header := r.Header.Get("Authorization")
path := r.URL.Path
// Ignore request to "/konnect/v1/userinfo" as this will cause endless loop when getting userinfo
// needs a better idea on how to not hardcode this
if header == "" || !strings.HasPrefix(header, "Bearer ") || path == "/konnect/v1/userinfo" {
next.ServeHTTP(w, r)
return
}
customCtx := context.WithValue(r.Context(), oauth2.HTTPClient, opt.HTTPClient)
// check if oidc provider is initialized
if oidcProvider == nil {
// Lazily initialize a provider
// provider needs to be cached as when it is created
// it will fetch the keys from the issuer using the .well-known
// endpoint
var err error
oidcProvider, err = opt.OIDCProviderFunc()
if err != nil {
opt.Logger.Error().Err(err).Msg("could not initialize oidc provider")
w.WriteHeader(http.StatusInternalServerError)
return
}
}
token := strings.TrimPrefix(header, "Bearer ")
// TODO cache userinfo for access token if we can determine the expiry (which works in case it is a jwt based access token)
oauth2Token := &oauth2.Token{
AccessToken: token,
}
// The claims we want to have
var claims ocisoidc.StandardClaims
userInfo, err := oidcProvider.UserInfo(customCtx, oauth2.StaticTokenSource(oauth2Token))
if err != nil {
opt.Logger.Error().Err(err).Str("token", token).Msg("Failed to get userinfo")
http.Error(w, ErrInvalidToken.Error(), http.StatusUnauthorized)
return
}
if err := userInfo.Claims(&claims); err != nil {
opt.Logger.Error().Err(err).Interface("userinfo", userInfo).Msg("failed to unmarshal userinfo claims")
w.WriteHeader(http.StatusInternalServerError)
return
}
//TODO: This should be read from the token instead of config
claims.Iss = opt.OIDCIss
// inject claims to the request context for the account_uuid middleware.
ctxWithClaims := ocisoidc.NewContext(r.Context(), &claims)
r = r.WithContext(ctxWithClaims)
opt.Logger.Debug().Interface("claims", claims).Interface("userInfo", userInfo).Msg("unmarshalled userinfo")
// store claims in context
// uses the original context, not the one with probably reduced security
nr := r.WithContext(ocisoidc.NewContext(r.Context(), &claims))
next.ServeHTTP(w, nr)
})
}
}
// AccountsCacheEntry stores a request to the accounts service on the cache.
// this type declaration should be on each respective service.
type AccountsCacheEntry struct {
Email string
UUID string
}
const (
// AccountsKey declares the svcKey for the Accounts service.
AccountsKey = "accounts"
// NodeKey declares the key that will be used to store the node address.
// It is shared between services.
NodeKey = "node"
)

View File

@@ -0,0 +1,67 @@
package middleware
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/coreos/go-oidc"
"github.com/owncloud/ocis-pkg/v2/log"
"golang.org/x/oauth2"
)
func TestOpenIDConnectMiddleware(t *testing.T) {
svcCache.Invalidate(AccountsKey, "success")
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
m := OpenIDConnect(
Logger(log.NewLogger()),
OIDCProviderFunc(func() (OIDCProvider, error) {
return mockOP(false), nil
}),
)(next)
r := httptest.NewRequest(http.MethodGet, "https://idp.example.com", nil)
r.Header.Set("Authorization", "Bearer sometoken")
w := httptest.NewRecorder()
m.ServeHTTP(w, r)
if w.Code != http.StatusInternalServerError {
t.Errorf("expected an internal server error")
}
}
type mockOIDCProvider struct {
UserInfoFunc func(ctx context.Context, ts oauth2.TokenSource) (*oidc.UserInfo, error)
}
// UserInfo will panic if the function has been called, but not mocked
func (m mockOIDCProvider) UserInfo(ctx context.Context, ts oauth2.TokenSource) (*oidc.UserInfo, error) {
if m.UserInfoFunc != nil {
return m.UserInfoFunc(ctx, ts)
}
panic("UserInfo was called in test but not mocked")
}
func mockOP(retErr bool) OIDCProvider {
if retErr {
return &mockOIDCProvider{
UserInfoFunc: func(ctx context.Context, ts oauth2.TokenSource) (*oidc.UserInfo, error) {
return nil, fmt.Errorf("error returned by mockOIDCProvider UserInfo")
},
}
}
return &mockOIDCProvider{
UserInfoFunc: func(ctx context.Context, ts oauth2.TokenSource) (*oidc.UserInfo, error) {
ui := &oidc.UserInfo{
// claims: private ...
}
return ui, nil
},
}
}

View File

@@ -0,0 +1,120 @@
package middleware
import (
settings "github.com/owncloud/ocis-settings/pkg/proto/v0"
"net/http"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
acc "github.com/owncloud/ocis-accounts/pkg/proto/v0"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-proxy/pkg/config"
storepb "github.com/owncloud/ocis-store/pkg/proto/v0"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
// Logger to use for logging, must be set
Logger log.Logger
// TokenManagerConfig for communicating with the reva token manager
TokenManagerConfig config.TokenManager
// HTTPClient to use for communication with the oidc provider
HTTPClient *http.Client
// AccountsClient for resolving accounts
AccountsClient acc.AccountsService
// SettingsRoleService for the roles API in settings
SettingsRoleService settings.RoleService
// OIDCProviderFunc to lazily initialize a provider, must be set for the oidcProvider middleware
OIDCProviderFunc func() (OIDCProvider, error)
// OIDCIss is the oidc-issuer
OIDCIss string
// RevaGatewayClient to send requests to the reva gateway
RevaGatewayClient gateway.GatewayAPIClient
// Store for persisting data
Store storepb.StoreService
// PreSignedURLConfig to configure the middleware
PreSignedURLConfig config.PreSignedURL
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(l log.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// TokenManagerConfig provides a function to set the token manger config option.
func TokenManagerConfig(cfg config.TokenManager) Option {
return func(o *Options) {
o.TokenManagerConfig = cfg
}
}
// HTTPClient provides a function to set the http client config option.
func HTTPClient(c *http.Client) Option {
return func(o *Options) {
o.HTTPClient = c
}
}
// AccountsClient provides a function to set the accounts client config option.
func AccountsClient(ac acc.AccountsService) Option {
return func(o *Options) {
o.AccountsClient = ac
}
}
// SettingsRoleService provides a function to set the role service option.
func SettingsRoleService(rc settings.RoleService) Option {
return func(o *Options) {
o.SettingsRoleService = rc
}
}
// OIDCProviderFunc provides a function to set the the oidc provider function option.
func OIDCProviderFunc(f func() (OIDCProvider, error)) Option {
return func(o *Options) {
o.OIDCProviderFunc = f
}
}
// OIDCIss sets the oidc issuer url
func OIDCIss(iss string) Option {
return func(o *Options) {
o.OIDCIss = iss
}
}
// RevaGatewayClient provides a function to set the the reva gateway service client option.
func RevaGatewayClient(gc gateway.GatewayAPIClient) Option {
return func(o *Options) {
o.RevaGatewayClient = gc
}
}
// Store provides a function to set the store option.
func Store(sc storepb.StoreService) Option {
return func(o *Options) {
o.Store = sc
}
}
// PreSignedURLConfig provides a function to set the PreSignedURL config
func PreSignedURLConfig(cfg config.PreSignedURL) Option {
return func(o *Options) {
o.PreSignedURLConfig = cfg
}
}

View File

@@ -0,0 +1,151 @@
package middleware
import (
"context"
"crypto/sha512"
"encoding/hex"
"net/http"
"strings"
"time"
"github.com/owncloud/ocis-pkg/v2/log"
ocisoidc "github.com/owncloud/ocis-pkg/v2/oidc"
"github.com/owncloud/ocis-proxy/pkg/config"
storepb "github.com/owncloud/ocis-store/pkg/proto/v0"
"golang.org/x/crypto/pbkdf2"
)
const (
iterations = 10000
keyLen = 32
)
// PresignedURL provides a middleware to check access secured by a presigned URL.
func PresignedURL(opts ...Option) func(next http.Handler) http.Handler {
opt := newOptions(opts...)
l := opt.Logger
cfg := opt.PreSignedURLConfig
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if isSignedRequest(r) {
if signedRequestIsValid(l, r, opt.Store, cfg) {
// use openid claims to let the account_uuid middleware do a lookup by username
claims := ocisoidc.StandardClaims{
OcisID: r.URL.Query().Get("OC-Credential"),
}
// inject claims to the request context for the account_uuid middleware
ctxWithClaims := ocisoidc.NewContext(r.Context(), &claims)
r = r.WithContext(ctxWithClaims)
next.ServeHTTP(w, r)
} else {
http.Error(w, "Invalid url signature", http.StatusUnauthorized)
return
}
}
next.ServeHTTP(w, r)
})
}
}
func isSignedRequest(r *http.Request) bool {
return r.URL.Query().Get("OC-Signature") != ""
}
func signedRequestIsValid(l log.Logger, r *http.Request, s storepb.StoreService, cfg config.PreSignedURL) bool {
// TODO OC-Algorithm - defined the used algo (e.g. sha256 or sha512 - we should agree on one default algo and make this parameter optional)
// TODO OC-Verb - defines for which http verb the request is valid - defaults to GET OPTIONAL
return allRequiredParametersArePresent(r) &&
requestMethodMatches(r) &&
requestMethodIsAllowed(r.Method, cfg.AllowedHTTPMethods) &&
!urlIsExpired(r, time.Now) &&
signatureIsValid(l, r, s)
}
func allRequiredParametersArePresent(r *http.Request) bool {
// OC-Credential - defines the user scope (shall we use the owncloud user id here - this might leak internal data ....) REQUIRED
// OC-Date - defined the date the url was signed (ISO 8601 UTC) REQUIRED
// OC-Expires - defines the expiry interval in seconds (between 1 and 604800 = 7 days) REQUIRED
// OC-Signature - the computed signature - server will verify the request upon this REQUIRED
return r.URL.Query().Get("OC-Signature") != "" &&
r.URL.Query().Get("OC-Credential") != "" &&
r.URL.Query().Get("OC-Date") != "" &&
r.URL.Query().Get("OC-Expires") != "" &&
r.URL.Query().Get("OC-Verb") != ""
}
func requestMethodMatches(r *http.Request) bool {
return strings.EqualFold(r.Method, r.URL.Query().Get("OC-Verb"))
}
func requestMethodIsAllowed(m string, allowedMethods []string) bool {
for _, allowed := range allowedMethods {
if strings.EqualFold(m, allowed) {
return true
}
}
return false
}
func urlIsExpired(r *http.Request, now func() time.Time) bool {
t, err := time.Parse(time.RFC3339, r.URL.Query().Get("OC-Date"))
if err != nil {
return true
}
expires, err := time.ParseDuration(r.URL.Query().Get("OC-Expires") + "s")
if err != nil {
return true
}
t.Add(expires)
return t.After(now())
}
func signatureIsValid(l log.Logger, r *http.Request, s storepb.StoreService) bool {
signingKey, err := getSigningKey(r.Context(), s, r.URL.Query().Get("OC-Credential"))
if err != nil {
l.Error().Err(err).Msg("could not retrieve signing key")
return false
}
if len(signingKey) == 0 {
l.Error().Err(err).Msg("signing key empty")
return false
}
q := r.URL.Query()
signature := q.Get("OC-Signature")
q.Del("OC-Signature")
r.URL.RawQuery = q.Encode()
url := r.URL.String()
if !r.URL.IsAbs() {
url = "https://" + r.Host + url // TODO where do we get the scheme from
}
return createSignature(url, signingKey) == signature
}
func createSignature(url string, signingKey []byte) string {
// the oc10 signature check: $hash = \hash_pbkdf2("sha512", $url, $signingKey, 10000, 64, false);
// - sets the length of the output string to 64
// - sets raw output to false -> if raw_output is FALSE length corresponds to twice the byte-length of the derived key (as every byte of the key is returned as two hexits).
// TODO change to length 128 in oc10?
// fo golangs pbkdf2.Key we need to use 32 because it will be encoded into 64 hexits later
hash := pbkdf2.Key([]byte(url), signingKey, iterations, keyLen, sha512.New)
return hex.EncodeToString(hash)
}
func getSigningKey(ctx context.Context, s storepb.StoreService, credential string) ([]byte, error) {
res, err := s.Read(ctx, &storepb.ReadRequest{
Options: &storepb.ReadOptions{
Database: "proxy",
Table: "signing-keys",
},
Key: credential,
})
if err != nil || len(res.Records) < 1 {
return []byte{}, err
}
return res.Records[0].Value, nil
}

View File

@@ -0,0 +1,121 @@
package middleware
import (
"net/http/httptest"
"testing"
"time"
)
func TestIsSignedRequest(t *testing.T) {
tests := []struct {
url string
expected bool
}{
{"https://example.com/example.jpg", false},
{"https://example.com/example.jpg?OC-Signature=something", true},
}
for _, tt := range tests {
r := httptest.NewRequest("", tt.url, nil)
result := isSignedRequest(r)
if result != tt.expected {
t.Errorf("with %s expected %t got %t", tt.url, tt.expected, result)
}
}
}
func TestAllRequiredParametersPresent(t *testing.T) {
baseURL := "https://example.com/example.jpg?"
tests := []struct {
params string
expected bool
}{
{"OC-Signature=something&OC-Credential=something&OC-Date=something&OC-Expires=something&OC-Verb=something", true},
{"OC-Credential=something&OC-Date=something&OC-Expires=something&OC-Verb=something", false},
{"OC-Signature=something&OC-Date=something&OC-Expires=something&OC-Verb=something", false},
{"OC-Signature=something&OC-Credential=something&OC-Expires=something&OC-Verb=something", false},
{"OC-Signature=something&OC-Credential=something&OC-Date=something&OC-Verb=something", false},
{"OC-Signature=something&OC-Credential=something&OC-Date=something&OC-Expires=something", false},
}
for _, tt := range tests {
r := httptest.NewRequest("", baseURL+tt.params, nil)
result := allRequiredParametersArePresent(r)
if result != tt.expected {
t.Errorf("with %s expected %t got %t", tt.params, tt.expected, result)
}
}
}
func TestRequestMethodMatches(t *testing.T) {
tests := []struct {
method string
url string
expected bool
}{
{"GET", "https://example.com/example.jpg?OC-Verb=GET", true},
{"GET", "https://example.com/example.jpg?OC-Verb=get", true},
{"POST", "https://example.com/example.jpg?OC-Verb=GET", false},
}
for _, tt := range tests {
r := httptest.NewRequest(tt.method, tt.url, nil)
result := requestMethodMatches(r)
if result != tt.expected {
t.Errorf("with method %s and url %s expected %t got %t", tt.method, tt.url, tt.expected, result)
}
}
}
func TestRequestMethodIsAllowed(t *testing.T) {
tests := []struct {
method string
allowed []string
expected bool
}{
{"GET", []string{}, false},
{"GET", []string{"POST"}, false},
{"GET", []string{"GET"}, true},
{"GET", []string{"get"}, true},
{"GET", []string{"POST", "GET"}, true},
}
for _, tt := range tests {
result := requestMethodIsAllowed(tt.method, tt.allowed)
if result != tt.expected {
t.Errorf("with method %s and allowed methods %s expected %t got %t", tt.method, tt.allowed, tt.expected, result)
}
}
}
func TestUrlIsExpired(t *testing.T) {
nowFunc := func() time.Time {
t, _ := time.Parse(time.RFC3339, "2020-08-19T15:12:43.478Z")
return t
}
tests := []struct {
url string
expected bool
}{
{"http://example.com/example.jpg?OC-Date=2020-08-19T15:02:43.478Z&OC-Expires=1200", false},
{"http://example.com/example.jpg?OC-Date=invalid&OC-Expires=1200", true},
{"http://example.com/example.jpg?OC-Date=2020-08-19T15:02:43.478Z&OC-Expires=invalid", true},
}
for _, tt := range tests {
r := httptest.NewRequest("", tt.url, nil)
result := urlIsExpired(r, nowFunc)
if result != tt.expected {
t.Errorf("with %s expected %t got %t", tt.url, tt.expected, result)
}
}
}
func TestCreateSignature(t *testing.T) {
expected := "27d2ebea381384af3179235114801dcd00f91e46f99fca72575301cf3948101d"
s := createSignature("something", []byte("somerandomkey"))
if s != expected {
t.Fail()
}
}

40
proxy/pkg/proxy/option.go Normal file
View File

@@ -0,0 +1,40 @@
package proxy
import (
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-proxy/pkg/config"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Config *config.Config
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}

View File

@@ -0,0 +1,113 @@
package policy
import (
"context"
"fmt"
"net/http"
"github.com/micro/go-micro/v2/client/grpc"
accounts "github.com/owncloud/ocis-accounts/pkg/proto/v0"
"github.com/owncloud/ocis-pkg/v2/oidc"
"github.com/owncloud/ocis-proxy/pkg/config"
)
var (
// ErrMultipleSelectors in case there is more then one selector configured.
ErrMultipleSelectors = fmt.Errorf("only one type of policy-selector (static or migration) can be configured")
// ErrSelectorConfigIncomplete if policy_selector conf is missing
ErrSelectorConfigIncomplete = fmt.Errorf("missing either \"static\" or \"migration\" configuration in policy_selector config ")
// ErrUnexpectedConfigError unexpected config error
ErrUnexpectedConfigError = fmt.Errorf("could not initialize policy-selector for given config")
)
// Selector is a function which selects a proxy-policy based on the request.
//
// A policy is a random name which identifies a set of proxy-routes:
//{
// "policies": [
// {
// "name": "us-east-1",
// "routes": [
// {
// "endpoint": "/",
// "backend": "https://backend.us.example.com:8080/app"
// }
// ]
// },
// {
// "name": "eu-ams-1",
// "routes": [
// {
// "endpoint": "/",
// "backend": "https://backend.eu.example.com:8080/app"
// }
// ]
// }
// ]
//}
type Selector func(ctx context.Context, r *http.Request) (string, error)
// LoadSelector constructs a specific policy-selector from a given configuration
func LoadSelector(cfg *config.PolicySelector) (Selector, error) {
if cfg.Migration != nil && cfg.Static != nil {
return nil, ErrMultipleSelectors
}
if cfg.Migration == nil && cfg.Static == nil {
return nil, ErrSelectorConfigIncomplete
}
if cfg.Static != nil {
return NewStaticSelector(cfg.Static), nil
}
if cfg.Migration != nil {
return NewMigrationSelector(
cfg.Migration,
accounts.NewAccountsService("com.owncloud.accounts", grpc.NewClient())), nil
}
return nil, ErrUnexpectedConfigError
}
// NewStaticSelector returns a selector which uses a pre-configured policy.
//
// Configuration:
//
// "policy_selector": {
// "static": {"policy" : "reva"}
// },
func NewStaticSelector(cfg *config.StaticSelectorConf) Selector {
return func(ctx context.Context, r *http.Request) (s string, err error) {
return cfg.Policy, nil
}
}
// NewMigrationSelector selects the policy based on the existence of the oidc "preferred_username" claim in the accounts-service.
// The policy for each case is configurable:
// "policy_selector": {
// "migration": {
// "acc_found_policy" : "reva",
// "acc_not_found_policy": "oc10",
// "unauthenticated_policy": "oc10"
// }
// },
//
// This selector can be used in migration-scenarios where some users have already migrated from ownCloud10 to OCIS and
// thus have an entry in ocis-accounts. All users without accounts entry are routed to the legacy ownCloud10 instance.
func NewMigrationSelector(cfg *config.MigrationSelectorConf, ss accounts.AccountsService) Selector {
var acc = ss
return func(ctx context.Context, r *http.Request) (s string, err error) {
var userID string
if claims := oidc.FromContext(r.Context()); claims != nil {
userID = claims.PreferredUsername
if _, err := acc.GetAccount(ctx, &accounts.GetAccountRequest{Id: userID}); err != nil {
return cfg.AccNotFoundPolicy, nil
}
return cfg.AccFoundPolicy, nil
}
return cfg.UnauthenticatedPolicy, nil
}
}

View File

@@ -0,0 +1,97 @@
package policy
import (
"context"
"fmt"
"net/http/httptest"
"testing"
"github.com/micro/go-micro/v2/client"
"github.com/owncloud/ocis-accounts/pkg/proto/v0"
"github.com/owncloud/ocis-pkg/v2/oidc"
"github.com/owncloud/ocis-proxy/pkg/config"
)
func TestStaticSelector(t *testing.T) {
ctx := context.Background()
req := httptest.NewRequest("GET", "https://example.org/foo", nil)
sel := NewStaticSelector(&config.StaticSelectorConf{Policy: "reva"})
want := "reva"
got, err := sel(ctx, req)
if got != want {
t.Errorf("Expected policy %v got %v", want, got)
}
if err != nil {
t.Errorf("Unexpected error %v", err)
}
sel = NewStaticSelector(&config.StaticSelectorConf{Policy: "foo"})
want = "foo"
got, err = sel(ctx, req)
if got != want {
t.Errorf("Expected policy %v got %v", want, got)
}
if err != nil {
t.Errorf("Unexpected error %v", err)
}
}
type testCase struct {
AccSvcShouldReturnError bool
Claims *oidc.StandardClaims
Expected string
}
func TestMigrationSelector(t *testing.T) {
cfg := config.MigrationSelectorConf{
AccFoundPolicy: "found",
AccNotFoundPolicy: "not_found",
UnauthenticatedPolicy: "unauth",
}
var tests = []testCase{
{true, &oidc.StandardClaims{PreferredUsername: "Hans"}, "not_found"},
{false, &oidc.StandardClaims{PreferredUsername: "Hans"}, "found"},
{false, nil, "unauth"},
}
for _, tc := range tests {
//t.Run(fmt.Sprintf("#%v", k), func(t *testing.T) {
// t.Parallel()
tc := tc
sut := NewMigrationSelector(&cfg, mockAccSvc(tc.AccSvcShouldReturnError))
r := httptest.NewRequest("GET", "https://example.com", nil)
ctx := oidc.NewContext(r.Context(), tc.Claims)
nr := r.WithContext(ctx)
got, err := sut(ctx, nr)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if got != tc.Expected {
t.Errorf("Expected Policy %v got %v", tc.Expected, got)
}
//})
}
}
func mockAccSvc(retErr bool) proto.AccountsService {
if retErr {
return &proto.MockAccountsService{
GetFunc: func(ctx context.Context, in *proto.GetAccountRequest, opts ...client.CallOption) (record *proto.Account, err error) {
return nil, fmt.Errorf("error returned by mockAccountsService GET")
},
}
}
return &proto.MockAccountsService{
GetFunc: func(ctx context.Context, in *proto.GetAccountRequest, opts ...client.CallOption) (record *proto.Account, err error) {
return &proto.Account{}, nil
},
}
}

385
proxy/pkg/proxy/proxy.go Normal file
View File

@@ -0,0 +1,385 @@
package proxy
import (
"context"
"net/http"
"net/http/httputil"
"net/url"
"regexp"
"strings"
"github.com/owncloud/ocis-proxy/pkg/proxy/policy"
"go.opencensus.io/plugin/ochttp/propagation/tracecontext"
"go.opencensus.io/trace"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-proxy/pkg/config"
)
// MultiHostReverseProxy extends httputil to support multiple hosts with diffent policies
type MultiHostReverseProxy struct {
httputil.ReverseProxy
Directors map[string]map[config.RouteType]map[string]func(req *http.Request)
PolicySelector policy.Selector
logger log.Logger
propagator tracecontext.HTTPFormat
config *config.Config
}
// NewMultiHostReverseProxy undocummented
func NewMultiHostReverseProxy(opts ...Option) *MultiHostReverseProxy {
options := newOptions(opts...)
rp := &MultiHostReverseProxy{
Directors: make(map[string]map[config.RouteType]map[string]func(req *http.Request)),
logger: options.Logger,
config: options.Config,
}
rp.Director = rp.directorSelectionDirector
if options.Config.Policies == nil {
rp.logger.Info().Str("source", "runtime").Msg("Policies")
options.Config.Policies = defaultPolicies()
} else {
rp.logger.Info().Str("source", "file").Msg("Policies")
}
if options.Config.PolicySelector == nil {
firstPolicy := options.Config.Policies[0].Name
rp.logger.Warn().Msgf("policy-selector not configured. Will always use first policy: '%v'", firstPolicy)
options.Config.PolicySelector = &config.PolicySelector{
Static: &config.StaticSelectorConf{
Policy: firstPolicy,
},
}
}
rp.logger.Debug().
Interface("selector_config", options.Config.PolicySelector).
Msg("loading policy-selector")
policySelector, err := policy.LoadSelector(options.Config.PolicySelector)
if err != nil {
rp.logger.Fatal().Err(err).Msg("Could not load policy-selector")
}
rp.PolicySelector = policySelector
for _, pol := range options.Config.Policies {
for _, route := range pol.Routes {
rp.logger.Debug().Str("fwd: ", route.Endpoint)
uri, err := url.Parse(route.Backend)
if err != nil {
rp.logger.
Fatal().
Err(err).
Msgf("malformed url: %v", route.Backend)
}
rp.logger.
Debug().
Interface("route", route).
Msg("adding route")
rp.AddHost(pol.Name, uri, route)
}
}
return rp
}
func (p *MultiHostReverseProxy) directorSelectionDirector(r *http.Request) {
pol, err := p.PolicySelector(r.Context(), r)
if err != nil {
p.logger.Error().Msgf("Error while selecting pol %v", err)
return
}
if _, ok := p.Directors[pol]; !ok {
p.logger.
Error().
Msgf("policy %v is not configured", pol)
return
}
// find matching director
for _, rt := range config.RouteTypes {
var handler func(string, url.URL) bool
switch rt {
case config.QueryRoute:
handler = p.queryRouteMatcher
case config.RegexRoute:
handler = p.regexRouteMatcher
case config.PrefixRoute:
fallthrough
default:
handler = p.prefixRouteMatcher
}
for endpoint := range p.Directors[pol][rt] {
if handler(endpoint, *r.URL) {
p.logger.
Debug().
Str("policy", pol).
Str("prefix", endpoint).
Str("path", r.URL.Path).
Str("routeType", string(rt)).
Msg("director found")
p.Directors[pol][rt][endpoint](r)
return
}
}
}
// override default director with root. If any
if p.Directors[pol][config.PrefixRoute]["/"] != nil {
p.Directors[pol][config.PrefixRoute]["/"](r)
return
}
p.logger.
Warn().
Str("policy", pol).
Str("path", r.URL.Path).
Msg("no director found")
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
// AddHost undocumented
func (p *MultiHostReverseProxy) AddHost(policy string, target *url.URL, rt config.Route) {
targetQuery := target.RawQuery
if p.Directors[policy] == nil {
p.Directors[policy] = make(map[config.RouteType]map[string]func(req *http.Request))
}
routeType := config.DefaultRouteType
if rt.Type != "" {
routeType = rt.Type
}
if p.Directors[policy][routeType] == nil {
p.Directors[policy][routeType] = make(map[string]func(req *http.Request))
}
p.Directors[policy][routeType][rt.Endpoint] = func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
// Apache deployments host addresses need to match on req.Host and req.URL.Host
// see https://stackoverflow.com/questions/34745654/golang-reverseproxy-with-apache2-sni-hostname-error
if rt.ApacheVHost {
req.Host = target.Host
}
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
if _, ok := req.Header["User-Agent"]; !ok {
// explicitly disable User-Agent so it's not set to default value
req.Header.Set("User-Agent", "")
}
}
}
func (p *MultiHostReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
var span *trace.Span
// Start root span.
if p.config.Tracing.Enabled {
ctx, span = trace.StartSpan(context.Background(), r.URL.String())
defer span.End()
p.propagator.SpanContextToRequest(span.SpanContext(), r)
}
// Call upstream ServeHTTP
p.ReverseProxy.ServeHTTP(w, r.WithContext(ctx))
}
func (p MultiHostReverseProxy) queryRouteMatcher(endpoint string, target url.URL) bool {
u, _ := url.Parse(endpoint)
if strings.HasPrefix(target.Path, u.Path) && endpoint != "/" {
query := u.Query()
if len(query) != 0 {
rQuery := target.Query()
match := true
for k := range query {
v := query.Get(k)
rv := rQuery.Get(k)
if rv != v {
match = false
break
}
}
return match
}
}
return false
}
func (p *MultiHostReverseProxy) regexRouteMatcher(endpoint string, target url.URL) bool {
matched, err := regexp.MatchString(endpoint, target.String())
if err != nil {
p.logger.Warn().Err(err).Msgf("regex with pattern %s failed", endpoint)
}
return matched
}
func (p *MultiHostReverseProxy) prefixRouteMatcher(endpoint string, target url.URL) bool {
return strings.HasPrefix(target.Path, endpoint) && endpoint != "/"
}
func defaultPolicies() []config.Policy {
return []config.Policy{
{
Name: "reva",
Routes: []config.Route{
{
Endpoint: "/",
Backend: "http://localhost:9100",
},
{
Endpoint: "/.well-known/",
Backend: "http://localhost:9130",
},
{
Endpoint: "/konnect/",
Backend: "http://localhost:9130",
},
{
Endpoint: "/signin/",
Backend: "http://localhost:9130",
},
{
Type: config.RegexRoute,
Endpoint: "/ocs/v[12].php/cloud/user", // we have `user` and `users` in ocis-ocs
Backend: "http://localhost:9110",
},
{
Endpoint: "/ocs/",
Backend: "http://localhost:9140",
},
{
Type: config.QueryRoute,
Endpoint: "/remote.php/?preview=1",
Backend: "http://localhost:9115",
},
{
Endpoint: "/remote.php/",
Backend: "http://localhost:9140",
},
{
Endpoint: "/dav/",
Backend: "http://localhost:9140",
},
{
Endpoint: "/webdav/",
Backend: "http://localhost:9140",
},
{
Endpoint: "/status.php",
Backend: "http://localhost:9140",
},
{
Endpoint: "/index.php/",
Backend: "http://localhost:9140",
},
{
Endpoint: "/data",
Backend: "http://localhost:9140",
},
// if we were using the go micro api gateway we could look up the endpoint in the registry dynamically
{
Endpoint: "/api/v0/accounts",
Backend: "http://localhost:9181",
},
// TODO the lookup needs a better mechanism
{
Endpoint: "/accounts.js",
Backend: "http://localhost:9181",
},
{
Endpoint: "/api/v0/settings",
Backend: "http://localhost:9190",
},
{
Endpoint: "/settings.js",
Backend: "http://localhost:9190",
},
{
Endpoint: "/api/v0/greet",
Backend: "http://localhost:9105",
},
{
Endpoint: "/hello.js",
Backend: "http://localhost:9105",
},
},
},
{
Name: "oc10",
Routes: []config.Route{
{
Endpoint: "/",
Backend: "http://localhost:9100",
},
{
Endpoint: "/.well-known/",
Backend: "http://localhost:9130",
},
{
Endpoint: "/konnect/",
Backend: "http://localhost:9130",
},
{
Endpoint: "/signin/",
Backend: "http://localhost:9130",
},
{
Endpoint: "/ocs/",
Backend: "https://demo.owncloud.com",
ApacheVHost: true,
},
{
Endpoint: "/remote.php/",
Backend: "https://demo.owncloud.com",
ApacheVHost: true,
},
{
Endpoint: "/dav/",
Backend: "https://demo.owncloud.com",
ApacheVHost: true,
},
{
Endpoint: "/webdav/",
Backend: "https://demo.owncloud.com",
ApacheVHost: true,
},
{
Endpoint: "/status.php",
Backend: "https://demo.owncloud.com",
ApacheVHost: true,
},
{
Endpoint: "/index.php/",
Backend: "https://demo.owncloud.com",
ApacheVHost: true,
},
{
Endpoint: "/data",
Backend: "https://demo.owncloud.com",
ApacheVHost: true,
},
},
},
}
}

View File

@@ -0,0 +1,222 @@
package proxy
import (
"bytes"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/owncloud/ocis-proxy/pkg/config"
)
func TestProxyIntegration(t *testing.T) {
var tests = []testCase{
// Simple prefix route
test("simple_prefix", withPolicy("reva", withRoutes{{
Type: config.PrefixRoute,
Endpoint: "/api",
Backend: "http://api.example.com"},
})).withRequest("GET", "https://example.com/api", nil).
expectProxyTo("http://api.example.com/api"),
// Complex prefix route, different method
test("complex_prefix_post", withPolicy("reva", withRoutes{{
Type: config.PrefixRoute,
Endpoint: "/api",
Backend: "http://api.example.com/service1/"},
})).withRequest("POST", "https://example.com/api", nil).
expectProxyTo("http://api.example.com/service1/api"),
// Query route
test("query_route", withPolicy("reva", withRoutes{{
Type: config.QueryRoute,
Endpoint: "/api?format=json",
Backend: "http://backend/"},
})).withRequest("GET", "https://example.com/api?format=json", nil).
expectProxyTo("http://backend/api?format=json"),
// Regex route
test("regex_route", withPolicy("reva", withRoutes{{
Type: config.RegexRoute,
Endpoint: `\/user\/(\d+)`,
Backend: "http://backend/"},
})).withRequest("POST", "https://example.com/user/1234", nil).
expectProxyTo("http://backend/user/1234"),
// Multiple prefix routes 1
test("multiple_prefix", withPolicy("reva", withRoutes{
{
Type: config.PrefixRoute,
Endpoint: "/api",
Backend: "http://api.example.com",
},
{
Type: config.PrefixRoute,
Endpoint: "/payment",
Backend: "http://payment.example.com",
},
})).withRequest("GET", "https://example.com/payment", nil).
expectProxyTo("http://payment.example.com/payment"),
// Multiple prefix routes 2
test("multiple_prefix", withPolicy("reva", withRoutes{
{
Type: config.PrefixRoute,
Endpoint: "/api",
Backend: "http://api.example.com",
},
{
Type: config.PrefixRoute,
Endpoint: "/payment",
Backend: "http://payment.example.com",
},
})).withRequest("GET", "https://example.com/api", nil).
expectProxyTo("http://api.example.com/api"),
// Mixed route types
test("mixed_types", withPolicy("reva", withRoutes{
{
Type: config.PrefixRoute,
Endpoint: "/api",
Backend: "http://api.example.com",
},
{
Type: config.RegexRoute,
Endpoint: `\/user\/(\d+)`,
Backend: "http://users.example.com",
ApacheVHost: false,
},
})).withRequest("GET", "https://example.com/api", nil).
expectProxyTo("http://api.example.com/api"),
// Mixed route types
test("mixed_types", withPolicy("reva", withRoutes{
{
Type: config.PrefixRoute,
Endpoint: "/api",
Backend: "http://api.example.com",
},
{
Type: config.RegexRoute,
Endpoint: `\/user\/(\d+)`,
Backend: "http://users.example.com",
ApacheVHost: false,
},
})).withRequest("GET", "https://example.com/user/1234", nil).
expectProxyTo("http://users.example.com/user/1234"),
}
for k := range tests {
t.Run(tests[k].id, func(t *testing.T) {
t.Parallel()
tc := tests[k]
rp := newTestProxy(testConfig(tc.conf), func(req *http.Request) *http.Response {
if got, want := req.URL.String(), tc.expect.String(); got != want {
t.Errorf("Proxied url should be %v got %v", want, got)
}
if got, want := req.Method, tc.input.Method; got != want {
t.Errorf("Proxied request method should be %v got %v", want, got)
}
if got, want := req.Proto, tc.input.Proto; got != want {
t.Errorf("Proxied request proto should be %v got %v", want, got)
}
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewBufferString(`OK`)),
Header: make(http.Header),
}
})
rr := httptest.NewRecorder()
rp.ServeHTTP(rr, tc.input)
if rr.Result().StatusCode != 200 {
t.Errorf("Expected status 200 from proxy-response got %v", rr.Result().StatusCode)
}
resultBody, err := ioutil.ReadAll(rr.Result().Body)
if err != nil {
t.Fatal("Error reading result body")
}
bodyString := string(resultBody)
if bodyString != `OK` {
t.Errorf("Result body of proxied response should be OK, got %v", bodyString)
}
})
}
}
func newTestProxy(cfg *config.Config, fn RoundTripFunc) *MultiHostReverseProxy {
rp := NewMultiHostReverseProxy(Config(cfg))
rp.Transport = fn
return rp
}
type RoundTripFunc func(req *http.Request) *http.Response
// RoundTrip .
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req), nil
}
type withRoutes []config.Route
type testCase struct {
id string
input *http.Request
expect *url.URL
conf []config.Policy
}
func test(id string, policies ...config.Policy) *testCase {
tc := &testCase{
id: id,
}
for k := range policies {
tc.conf = append(tc.conf, policies[k])
}
return tc
}
func withPolicy(name string, r withRoutes) config.Policy {
return config.Policy{Name: name, Routes: r}
}
func (tc *testCase) withRequest(method string, target string, body io.Reader) *testCase {
tc.input = httptest.NewRequest(method, target, body)
return tc
}
func (tc *testCase) expectProxyTo(strURL string) testCase {
pu, err := url.Parse(strURL)
if err != nil {
log.Fatalf("Error parsing %v", strURL)
}
tc.expect = pu
return *tc
}
func testConfig(policy []config.Policy) *config.Config {
return &config.Config{
File: "",
Log: config.Log{},
Debug: config.Debug{},
HTTP: config.HTTP{},
Tracing: config.Tracing{},
Asset: config.Asset{},
Policies: policy,
OIDC: config.OIDC{},
PolicySelector: nil,
}
}

View File

@@ -0,0 +1,112 @@
package proxy
import (
"net/url"
"testing"
"github.com/owncloud/ocis-proxy/pkg/config"
)
func TestPrefixRouteMatcher(t *testing.T) {
cfg := config.New()
p := NewMultiHostReverseProxy(Config(cfg))
endpoint := "/foobar"
u, _ := url.Parse("/foobar/baz/some/url")
matched := p.prefixRouteMatcher(endpoint, *u)
if !matched {
t.Errorf("Endpoint %s and URL %s should match", endpoint, u.String())
}
}
func TestQueryRouteMatcher(t *testing.T) {
cfg := config.New()
p := NewMultiHostReverseProxy(Config(cfg))
endpoint := "/foobar?parameter=true"
u, _ := url.Parse("/foobar/baz/some/url?parameter=true")
matched := p.queryRouteMatcher(endpoint, *u)
if !matched {
t.Errorf("Endpoint %s and URL %s should match", endpoint, u.String())
}
}
func TestQueryRouteMatcherWithoutParameters(t *testing.T) {
cfg := config.New()
p := NewMultiHostReverseProxy(Config(cfg))
endpoint := "/foobar"
u, _ := url.Parse("/foobar/baz/some/url?parameter=true")
matched := p.queryRouteMatcher(endpoint, *u)
if matched {
t.Errorf("Endpoint %s and URL %s should not match", endpoint, u.String())
}
}
func TestQueryRouteMatcherWithDifferingParameters(t *testing.T) {
cfg := config.New()
p := NewMultiHostReverseProxy(Config(cfg))
endpoint := "/foobar?parameter=false"
u, _ := url.Parse("/foobar/baz/some/url?parameter=true")
matched := p.queryRouteMatcher(endpoint, *u)
if matched {
t.Errorf("Endpoint %s and URL %s should not match", endpoint, u.String())
}
}
func TestQueryRouteMatcherWithMultipleDifferingParameters(t *testing.T) {
cfg := config.New()
p := NewMultiHostReverseProxy(Config(cfg))
endpoint := "/foobar?parameter=false&other=true"
u, _ := url.Parse("/foobar/baz/some/url?parameter=true")
matched := p.queryRouteMatcher(endpoint, *u)
if matched {
t.Errorf("Endpoint %s and URL %s should not match", endpoint, u.String())
}
}
func TestQueryRouteMatcherWithMultipleParameters(t *testing.T) {
cfg := config.New()
p := NewMultiHostReverseProxy(Config(cfg))
endpoint := "/foobar?parameter=false&other=true"
u, _ := url.Parse("/foobar/baz/some/url?parameter=false&other=true")
matched := p.queryRouteMatcher(endpoint, *u)
if !matched {
t.Errorf("Endpoint %s and URL %s should match", endpoint, u.String())
}
}
func TestRegexRouteMatcher(t *testing.T) {
cfg := config.New()
p := NewMultiHostReverseProxy(Config(cfg))
endpoint := ".*some\\/url.*parameter=true"
u, _ := url.Parse("/foobar/baz/some/url?parameter=true")
matched := p.regexRouteMatcher(endpoint, *u)
if !matched {
t.Errorf("Endpoint %s and URL %s should match", endpoint, u.String())
}
}
func TestRegexRouteMatcherWithInvalidPattern(t *testing.T) {
cfg := config.New()
p := NewMultiHostReverseProxy(Config(cfg))
endpoint := "([\\])\\w+"
u, _ := url.Parse("/foobar/baz/some/url?parameter=true")
matched := p.regexRouteMatcher(endpoint, *u)
if matched {
t.Errorf("Endpoint %s and URL %s should not match", endpoint, u.String())
}
}

Some files were not shown because too many files have changed in this diff Show More