mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-30 17:00:57 -06:00
Add 'proxy/' from commit '201b9a652685cdfb72ba81c7e7b00ba1c60a0e35'
git-subtree-dir: proxy git-subtree-mainline:571d96e856git-subtree-split:201b9a6526
This commit is contained in:
11
proxy/.codacy.yml
Normal file
11
proxy/.codacy.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
exclude_paths:
|
||||
- CHANGELOG.md
|
||||
- changelog/**
|
||||
- docs/**
|
||||
- config/**
|
||||
- pkg/proto/**
|
||||
- '**_test.go'
|
||||
- .codacy.yml
|
||||
|
||||
...
|
||||
2
proxy/.dockerignore
Normal file
2
proxy/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!bin/
|
||||
698
proxy/.drone.star
Normal file
698
proxy/.drone.star
Normal 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
35
proxy/.editorconfig
Normal 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
12
proxy/.github/config.yml
vendored
Normal 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
0
proxy/.github/issue_template.md
vendored
Normal file
0
proxy/.github/pull_request_template.md
vendored
Normal file
0
proxy/.github/pull_request_template.md
vendored
Normal file
98
proxy/.github/settings.yml
vendored
Normal file
98
proxy/.github/settings.yml
vendored
Normal 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
5
proxy/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
coverage.out
|
||||
|
||||
/bin
|
||||
/dist
|
||||
/hugo
|
||||
33
proxy/.golangci.yaml
Normal file
33
proxy/.golangci.yaml
Normal 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
478
proxy/CHANGELOG.md
Normal 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
202
proxy/LICENSE
Normal 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
156
proxy/Makefile
Normal 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
45
proxy/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# ownCloud Infinite Scale: Proxy
|
||||
|
||||
[](https://cloud.drone.io/owncloud/ocis-proxy)
|
||||
[](https://www.codacy.com/gh/owncloud/ocis-proxy?utm_source=github.com&utm_medium=referral&utm_content=owncloud/ocis-bridge&utm_campaign=Badge_Grade)
|
||||
[](https://www.codacy.com/gh/owncloud/ocis-proxy?utm_source=github.com&utm_medium=referral&utm_content=owncloud/ocis-bridge&utm_campaign=Badge_Coverage)
|
||||
[](http://godoc.org/github.com/owncloud/ocis-proxy)
|
||||
[](http://goreportcard.com/report/github.com/owncloud/ocis-proxy)
|
||||
[](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>
|
||||
```
|
||||
5
proxy/changelog/0.1.0_2020-03-18/inital-release
Normal file
5
proxy/changelog/0.1.0_2020-03-18/inital-release
Normal 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
|
||||
7
proxy/changelog/0.1.0_2020-03-18/runtime-policies.md
Normal file
7
proxy/changelog/0.1.0_2020-03-18/runtime-policies.md
Normal 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
|
||||
@@ -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
|
||||
5
proxy/changelog/0.2.0_2020-03-25/client-urls.md
Normal file
5
proxy/changelog/0.2.0_2020-03-25/client-urls.md
Normal 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
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
Bugfix: Set TLS-Certificate correctly
|
||||
|
||||
https://github.com/owncloud/ocis-proxy/pull/25
|
||||
8
proxy/changelog/0.3.0_2020-03-30/add-oidc.md
Normal file
8
proxy/changelog/0.3.0_2020-03-30/add-oidc.md
Normal 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
|
||||
|
||||
10
proxy/changelog/0.3.0_2020-03-30/policy_selectors.md
Normal file
10
proxy/changelog/0.3.0_2020-03-30/policy_selectors.md
Normal 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
|
||||
|
||||
4
proxy/changelog/0.3.0_2020-03-30/redirect_to_https.md
Normal file
4
proxy/changelog/0.3.0_2020-03-30/redirect_to_https.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Change: Insecure http-requests are now redirected to https
|
||||
|
||||
https://github.com/owncloud/ocis-proxy/pull/29
|
||||
|
||||
5
proxy/changelog/0.3.1_2020-03-31/update-pkg
Normal file
5
proxy/changelog/0.3.1_2020-03-31/update-pkg
Normal 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
|
||||
5
proxy/changelog/0.4.0_2020-06-25/account-uuid
Normal file
5
proxy/changelog/0.4.0_2020-06-25/account-uuid
Normal 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
|
||||
@@ -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
|
||||
6
proxy/changelog/0.4.0_2020-06-25/cache-miss-fix
Normal file
6
proxy/changelog/0.4.0_2020-06-25/cache-miss-fix
Normal 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
|
||||
7
proxy/changelog/0.4.0_2020-06-25/disable-oidc-keep-alive
Normal file
7
proxy/changelog/0.4.0_2020-06-25/disable-oidc-keep-alive
Normal 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
|
||||
5
proxy/changelog/0.4.0_2020-06-25/jwt-secret-config
Normal file
5
proxy/changelog/0.4.0_2020-06-25/jwt-secret-config
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
5
proxy/changelog/0.4.0_2020-06-25/send-autocreate-home
Normal file
5
proxy/changelog/0.4.0_2020-06-25/send-autocreate-home
Normal 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
|
||||
6
proxy/changelog/0.4.0_2020-06-25/token-header-fix
Normal file
6
proxy/changelog/0.4.0_2020-06-25/token-header-fix
Normal 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
|
||||
@@ -0,0 +1,5 @@
|
||||
Change: Update to new accounts API
|
||||
|
||||
Update to new accounts API
|
||||
|
||||
https://github.com/owncloud/ocis-proxy/issues/39
|
||||
@@ -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
|
||||
9
proxy/changelog/0.5.0_2020-07-23/add-disable-tls.md
Normal file
9
proxy/changelog/0.5.0_2020-07-23/add-disable-tls.md
Normal 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
|
||||
11
proxy/changelog/0.5.0_2020-07-23/add-oidc-config-flags.md
Normal file
11
proxy/changelog/0.5.0_2020-07-23/add-oidc-config-flags.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
5
proxy/changelog/0.5.0_2020-07-23/root-tracing.md
Normal file
5
proxy/changelog/0.5.0_2020-07-23/root-tracing.md
Normal 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
|
||||
8
proxy/changelog/0.5.0_2020-07-23/support-signed-urls.md
Normal file
8
proxy/changelog/0.5.0_2020-07-23/support-signed-urls.md
Normal 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
|
||||
@@ -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
|
||||
5
proxy/changelog/0.6.0_2020-08-17/enable-new-accounts.md
Normal file
5
proxy/changelog/0.6.0_2020-08-17/enable-new-accounts.md
Normal 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
|
||||
@@ -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
|
||||
5
proxy/changelog/0.6.0_2020-08-17/settings-and-ocs
Normal file
5
proxy/changelog/0.6.0_2020-08-17/settings-and-ocs
Normal 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
|
||||
5
proxy/changelog/0.6.0_2020-08-17/use-alpine-latest.md
Normal file
5
proxy/changelog/0.6.0_2020-08-17/use-alpine-latest.md
Normal 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
|
||||
@@ -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
|
||||
5
proxy/changelog/0.7.0_2020-08-21/mint-uid-and-gid.md
Normal file
5
proxy/changelog/0.7.0_2020-08-21/mint-uid-and-gid.md
Normal 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
|
||||
@@ -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
|
||||
53
proxy/changelog/CHANGELOG.tmpl
Normal file
53
proxy/changelog/CHANGELOG.tmpl
Normal 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 -}}
|
||||
6
proxy/changelog/README.md
Normal file
6
proxy/changelog/README.md
Normal 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
11
proxy/changelog/TEMPLATE
Normal 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
|
||||
0
proxy/changelog/unreleased/.keep
Normal file
0
proxy/changelog/unreleased/.keep
Normal file
5
proxy/changelog/unreleased/fix-director-selection.md
Normal file
5
proxy/changelog/unreleased/fix-director-selection.md
Normal 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
|
||||
5
proxy/changelog/unreleased/hello-in-example-config.md
Normal file
5
proxy/changelog/unreleased/hello-in-example-config.md
Normal 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
|
||||
5
proxy/changelog/unreleased/mint-roles.md
Normal file
5
proxy/changelog/unreleased/mint-roles.md
Normal 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
|
||||
5
proxy/changelog/unreleased/remove-account-caching.md
Normal file
5
proxy/changelog/unreleased/remove-account-caching.md
Normal 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
|
||||
6
proxy/changelog/unreleased/settings-in-example-config.md
Normal file
6
proxy/changelog/unreleased/settings-in-example-config.md
Normal 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
|
||||
13
proxy/cmd/ocis-proxy/main.go
Normal file
13
proxy/cmd/ocis-proxy/main.go
Normal 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)
|
||||
}
|
||||
}
|
||||
137
proxy/config/proxy-example-migration.json
Normal file
137
proxy/config/proxy-example-migration.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
148
proxy/config/proxy-example.json
Normal file
148
proxy/config/proxy-example.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
19
proxy/docker/Dockerfile.linux.amd64
Normal file
19
proxy/docker/Dockerfile.linux.amd64
Normal 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
|
||||
19
proxy/docker/Dockerfile.linux.arm
Normal file
19
proxy/docker/Dockerfile.linux.arm
Normal 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
|
||||
19
proxy/docker/Dockerfile.linux.arm64
Normal file
19
proxy/docker/Dockerfile.linux.arm64
Normal 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
|
||||
22
proxy/docker/manifest.tmpl
Normal file
22
proxy/docker/manifest.tmpl
Normal 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
8
proxy/docs/_index.md
Normal 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
10
proxy/docs/about.md
Normal 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
28
proxy/docs/building.md
Normal 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
119
proxy/docs/configuration.md
Normal 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`.
|
||||
|
||||
46
proxy/docs/getting-started.md
Normal file
46
proxy/docs/getting-started.md
Normal 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
31
proxy/go.mod
Normal 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
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
101
proxy/pkg/cache/cache.go
vendored
Normal 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
99
proxy/pkg/cache/cache_test.go
vendored
Normal 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
36
proxy/pkg/cache/option.go
vendored
Normal 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
|
||||
}
|
||||
49
proxy/pkg/command/health.go
Normal file
49
proxy/pkg/command/health.go
Normal 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
108
proxy/pkg/command/root.go
Normal 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
319
proxy/pkg/command/server.go
Normal 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
134
proxy/pkg/config/config.go
Normal 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
123
proxy/pkg/crypto/gencert.go
Normal 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
26
proxy/pkg/cs3/client.go
Normal 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
|
||||
}
|
||||
206
proxy/pkg/flagset/flagset.go
Normal file
206
proxy/pkg/flagset/flagset.go
Normal 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"},
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
58
proxy/pkg/metrics/metrics.go
Normal file
58
proxy/pkg/metrics/metrics.go
Normal 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
|
||||
}
|
||||
189
proxy/pkg/middleware/account_uuid.go
Normal file
189
proxy/pkg/middleware/account_uuid.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
106
proxy/pkg/middleware/account_uuid_test.go
Normal file
106
proxy/pkg/middleware/account_uuid_test.go
Normal 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
|
||||
},
|
||||
}
|
||||
}
|
||||
77
proxy/pkg/middleware/create_home.go
Normal file
77
proxy/pkg/middleware/create_home.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
19
proxy/pkg/middleware/https_redirect.go
Normal file
19
proxy/pkg/middleware/https_redirect.go
Normal 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)
|
||||
})
|
||||
}
|
||||
6
proxy/pkg/middleware/middleware.go
Normal file
6
proxy/pkg/middleware/middleware.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package middleware
|
||||
|
||||
import "net/http"
|
||||
|
||||
// M undocummented
|
||||
type M func(next http.Handler) http.Handler
|
||||
119
proxy/pkg/middleware/openidconnect.go
Normal file
119
proxy/pkg/middleware/openidconnect.go
Normal 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"
|
||||
)
|
||||
67
proxy/pkg/middleware/openidconnect_test.go
Normal file
67
proxy/pkg/middleware/openidconnect_test.go
Normal 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
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
120
proxy/pkg/middleware/options.go
Normal file
120
proxy/pkg/middleware/options.go
Normal 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
|
||||
}
|
||||
}
|
||||
151
proxy/pkg/middleware/presigned_url.go
Normal file
151
proxy/pkg/middleware/presigned_url.go
Normal 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
|
||||
}
|
||||
121
proxy/pkg/middleware/presigned_url_test.go
Normal file
121
proxy/pkg/middleware/presigned_url_test.go
Normal 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
40
proxy/pkg/proxy/option.go
Normal 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
|
||||
}
|
||||
}
|
||||
113
proxy/pkg/proxy/policy/selector.go
Normal file
113
proxy/pkg/proxy/policy/selector.go
Normal 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
|
||||
}
|
||||
}
|
||||
97
proxy/pkg/proxy/policy/selector_test.go
Normal file
97
proxy/pkg/proxy/policy/selector_test.go
Normal 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
385
proxy/pkg/proxy/proxy.go
Normal 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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
222
proxy/pkg/proxy/proxy_integration_test.go
Normal file
222
proxy/pkg/proxy/proxy_integration_test.go
Normal 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,
|
||||
}
|
||||
}
|
||||
112
proxy/pkg/proxy/proxy_test.go
Normal file
112
proxy/pkg/proxy/proxy_test.go
Normal 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
Reference in New Issue
Block a user