Compare commits

..

170 Commits

Author SHA1 Message Date
39ad9d7c9d release: 2021.7.1-rc1 2021-07-21 10:44:40 +02:00
20d09c14b2 website/docs: add 2021.7
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 09:41:49 +02:00
3a4d514bae build(deps): bump @babel/core from 7.14.6 to 7.14.8 in /web (#1162)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.14.6 to 7.14.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.14.8/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-21 09:41:16 +02:00
4932846e14 build(deps): bump codemirror from 5.62.0 to 5.62.1 in /web (#1163)
Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.62.0 to 5.62.1.
- [Release notes](https://github.com/codemirror/CodeMirror/releases)
- [Changelog](https://github.com/codemirror/CodeMirror/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codemirror/CodeMirror/compare/5.62.0...5.62.1)

---
updated-dependencies:
- dependency-name: codemirror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-21 09:41:08 +02:00
bb62aa7c7f build(deps): bump actions/setup-node from 2.2.0 to 2.3.0 (#1165) 2021-07-21 09:19:25 +02:00
907b837301 build(deps): bump @babel/preset-env from 7.14.7 to 7.14.8 in /web (#1164) 2021-07-21 09:18:55 +02:00
b60a3d45dc build(deps): bump boto3 from 1.18.2 to 1.18.3 (#1166) 2021-07-21 09:18:43 +02:00
3f5585ca84 build(deps-dev): bump pylint from 2.9.3 to 2.9.4 (#1167) 2021-07-21 09:18:03 +02:00
ba9a4efc9b providers/oauth2: fix nonce field not being optional
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 00:34:01 +02:00
902378af53 providers/oauth2: fix redirect_uris not having blank set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 00:22:09 +02:00
2352a7f4d6 providers/oauth2: nonce is only required for implicit flows, don't check or fallback for other flows
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 00:21:08 +02:00
d89266a9d2 outposts/ldap: fix order of Listeners
TCP -> PROXY -> TLS

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-20 15:25:11 +02:00
d678d33756 root: add support for PROXY protocol on listeners
closes #1161

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-20 11:03:09 +02:00
49d0ccd9c7 build(deps): bump @typescript-eslint/parser in /web (#1158)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.28.3 to 4.28.4.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.28.4/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-20 09:08:16 +02:00
ea082ed9ef build(deps): bump @typescript-eslint/eslint-plugin in /web (#1159) 2021-07-20 08:33:22 +02:00
d62fc9766c build(deps): bump boto3 from 1.18.1 to 1.18.2 (#1160) 2021-07-20 08:33:12 +02:00
983747b13b website: add sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 21:50:56 +02:00
de4710ea71 outpost: minor cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 17:19:48 +02:00
d55b31dd82 outposts/proxy: set server header
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 17:11:11 +02:00
d87871f806 outposts/ldap: improve logging, add request ID
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 13:41:29 +02:00
148194e12b tests/e2e: add LDAPS bind tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 13:26:36 +02:00
a2c587be43 outposts: don't authenticate as service user for flows to set remote-ip
set outpost token as additional header and check that token (user) if they can override remote-ip

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 13:17:13 +02:00
673da2a96e build(deps): bump eslint from 7.30.0 to 7.31.0 in /web (#1156)
Bumps [eslint](https://github.com/eslint/eslint) from 7.30.0 to 7.31.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v7.30.0...v7.31.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-19 10:15:32 +02:00
a9a7b26264 build(deps): bump ldap3 from 2.9 to 2.9.1 (#1157)
Bumps [ldap3](https://github.com/cannatag/ldap3) from 2.9 to 2.9.1.
- [Release notes](https://github.com/cannatag/ldap3/releases)
- [Changelog](https://github.com/cannatag/ldap3/blob/dev/_changelog.txt)
- [Commits](https://github.com/cannatag/ldap3/compare/v2.9...v2.9.1)

---
updated-dependencies:
- dependency-name: ldap3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-19 10:11:30 +02:00
83d2c442a5 tests/e2e: fix ldap tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 22:43:35 +02:00
4029e19b72 outposts/ldap: fix order of flow check
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 22:22:35 +02:00
538a466090 root: fix middleware exception for outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 22:10:50 +02:00
322a343c81 root: fix log level not being set to DEBUG for tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 21:45:08 +02:00
6ddd6bfa72 root: fix linting errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 20:54:34 +02:00
36de302250 outposts: separate CLI flow executor from ldap
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 15:51:48 +02:00
9eb13c50e9 ci: fix linter for embed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 21:56:42 +02:00
cffc6a1b88 outpost/ldap: fix import
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 20:02:36 +02:00
ba437beacc build(deps): bump @rollup/plugin-replace from 2.4.2 to 3.0.0 in /web (#1152)
Bumps [@rollup/plugin-replace](https://github.com/rollup/plugins/tree/HEAD/packages/replace) from 2.4.2 to 3.0.0.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/replace/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/wasm-v3.0.0/packages/replace)

---
updated-dependencies:
- dependency-name: "@rollup/plugin-replace"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-17 19:38:57 +02:00
da32b05eba build(deps): bump boto3 from 1.18.0 to 1.18.1 (#1154)
Bumps [boto3](https://github.com/boto/boto3) from 1.18.0 to 1.18.1.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.18.0...1.18.1)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-17 19:38:44 +02:00
45b7e7565d Merge pull request #1153 from goauthentik/dependabot/go_modules/github.com/google/uuid-1.3.0
build(deps): bump github.com/google/uuid from 1.2.0 to 1.3.0
2021-07-17 19:38:33 +02:00
a0b63f50bf outposts: fix import for self-signed cert on ldap
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 19:38:04 +02:00
dc5d571c99 root: initial merging of outpost and main project (#1030)
* root: initial merging of outpost and main project

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* root: fix build for main server

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* root: start deduplicating code

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* root: add more common utils

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* outposts: make outpost managed

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* outposts: make managed outposts

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* root: more code merging

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* outposts: fix linting

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* root: fix missing go client in dockerfile

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* root: fix docker stage name

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* internal: fix gunicorn not being restarted correctly

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* internal: don't send kill signal to child as we mange it

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* cmd: fix shutdown not being signaled properl

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 18:38:27 +02:00
05161db458 cmd: fix shutdown not being signaled properl
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 18:04:09 +02:00
311ffa9f79 internal: don't send kill signal to child as we mange it
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 17:07:35 +02:00
7cbe33d65d internal: fix gunicorn not being restarted correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 16:59:31 +02:00
be9ca48de0 root: fix docker stage name
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 16:40:55 +02:00
b3159a74e5 Merge branch 'master' into inbuilt-proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	Dockerfile
#	internal/outpost/ak/api.go
#	internal/outpost/ak/api_uag.go
#	internal/outpost/ak/global.go
#	internal/outpost/ldap/api_tls.go
#	internal/outpost/ldap/instance_bind.go
#	internal/outpost/ldap/utils.go
#	internal/outpost/proxy/api_bundle.go
#	outpost/go.mod
#	outpost/go.sum
#	outpost/pkg/ak/cert.go
2021-07-17 12:49:38 +02:00
89fafff0af lifecycle: fix postgresql port not being passed for migrations
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-16 12:04:36 +02:00
ae77c872a0 root: celery requires additional parameters when tls is enabled (#1148) 2021-07-16 08:51:09 +02:00
5f13563e03 build(deps): bump rollup from 2.53.1 to 2.53.2 in /web (#1149) 2021-07-16 08:48:48 +02:00
e17c9040bb build(deps): bump @rollup/plugin-typescript from 8.2.1 to 8.2.3 in /web (#1150) 2021-07-16 08:48:40 +02:00
280ef3d265 build(deps): bump boto3 from 1.17.112 to 1.18.0 (#1151) 2021-07-16 08:48:30 +02:00
a5bb583268 root: optional TLS support on redis connections (#1147)
* root: optional TLS support on redis connections

* root: don't use f-strings when not interpolating variables

* root: use f-string in redis protocol prefix interpolation

* root: glaring typo

* formatting

* small formatting change I missed

* root: swap around default redis protocol prefixes
2021-07-15 11:48:52 +02:00
212ff11b6d api: fix Capabilities check for s3 backup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-15 09:58:07 +02:00
1fa9d70945 build(deps): bump golang from 1.16.5 to 1.16.6 (#1144) 2021-07-15 08:39:38 +02:00
eeeaa9317b build(deps): bump golang from 1.16.5 to 1.16.6 in /outpost (#1145) 2021-07-15 08:39:26 +02:00
09b932100f build(deps): bump boto3 from 1.17.111 to 1.17.112 (#1146) 2021-07-15 08:39:17 +02:00
aa701c5725 core: don't delete expired tokens, rotate their key
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 21:47:32 +02:00
6f98833150 core: allow users to create non-expiring tokens when flag is set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 21:15:14 +02:00
30aa24ce6e outposts/ldap: more cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 20:37:27 +02:00
a426a1a0b6 outposts: cleanup UserAgent config for API Client
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 20:33:35 +02:00
061c549a40 providers/ldap: fix: dn and member fields for virtual groups (#1143)
* providers/ldap: fix: dn and member fields for virtual groups

* Refactor GetGroupDN to use string name instead to allow more flexibility
2021-07-14 14:54:55 +00:00
efa09d5e1d providers/ldap: fix: Return user DN with virtual group (#1142)
* fix: incorrect ldap virtual group member DN

Signed-off-by: Toboshii Nakama <toboshii@gmail.com>

* fix: imports

Signed-off-by: Toboshii Nakama <toboshii@gmail.com>
2021-07-14 10:59:40 +00:00
4fe0bd4b6c tests/e2e: fix e2e tests for ldap provider
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 10:10:11 +02:00
7c2decf5ec providers/ldap: squash migrations
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 09:22:25 +02:00
7f39399c32 providers/ldap: Added auto-generated uidNumber and guidNumber generated attributes for use with SSSD and similar software. (#1138)
* Added auto-generated uidNumber and guidNumber generated attributes for
use with SSSD and similar software.

The starting number for uid/gid can be configured iva environtment
variables and is by default 2000 which should work fine for most instances unless there are more than
999 local accounts on the server/computer.

The uidNumber is just the users Pk + the starting number.
The guidNumber is calculated by the last couple of bytes in the uuid of
the group + the starting number, this should have a low enough chance
for collisions that it's going to be fine for most use cases.

I have not added any interface stuff for configuring the environment variables as I couldn't really find my way around all the places I'd have to edit to add it and the default values should in my opinion be fine for 99% use cases.

* Add a 'fake' primary group for each user

* First attempt att adding config to interface

* Updated API to support new fields

* Refactor code, update documentation and remove obsolete comment

Simplify `GetRIDForGroup`, was a bit overcomplicated before.

Add an additional class/struct `LDAPGroup` which is the new argument
for `pi.GroupEntry` and util functions to create `LDAPGroup` from api.Group and api.User

Add proper support in the interface for changing gidNumber and uidNumber starting points

* make lint-fix for the migration files
2021-07-14 09:17:01 +02:00
7fd78a591d build(deps): bump boto3 from 1.17.110 to 1.17.111 (#1141) 2021-07-14 08:44:03 +02:00
bdb84b7a8f root: build bundled docs into helo dir to fix path issue with packaged static files
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 19:09:16 +02:00
84e9748340 policies/reputation: handle cache error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 18:47:32 +02:00
7dfc621ae4 LDAP Provider: TLS support (#1137) 2021-07-13 18:24:18 +02:00
cd0a6f2d7c website: upgrade to docusaurus 2beta3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 12:46:29 +02:00
b7835a751b website: migrate to react-before-after-slider-component
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 12:10:08 +02:00
fd197ceee7 website: fix broken links
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 12:02:14 +02:00
be5c8341d2 root: add bundled docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 11:06:51 +02:00
2036827f04 api: add sentry tunnel
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 10:58:14 +02:00
35665d248e build(deps): bump @typescript-eslint/eslint-plugin in /web (#1131)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.28.2 to 4.28.3.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.28.3/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-13 10:34:27 +02:00
bc30b41157 build(deps): bump @sentry/browser from 6.8.0 to 6.9.0 in /web (#1130)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 6.8.0 to 6.9.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.8.0...6.9.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-13 10:34:14 +02:00
2af7fab42c build(deps): bump @typescript-eslint/parser in /web (#1132) 2021-07-13 08:41:24 +02:00
4de205809b build(deps): bump @sentry/tracing from 6.8.0 to 6.9.0 in /web (#1133) 2021-07-13 08:41:14 +02:00
e8433472fd build(deps): bump boto3 from 1.17.109 to 1.17.110 (#1134) 2021-07-13 08:40:40 +02:00
3896299312 build(deps): bump github.com/google/uuid from 1.2.0 to 1.3.0 in /outpost (#1135) 2021-07-13 08:40:32 +02:00
5cfbb0993a Allow for Configurable Redis Port (#1124)
* root: make redis port configurable

* root: parse redis port from config as an integer

* code formatting

* lifecycle: truncate line under 100 chars

* lifecycle: incorrect indenting on newline
2021-07-12 11:01:41 +02:00
a62e3557ac build(deps): bump rollup from 2.52.8 to 2.53.1 in /web (#1125)
Bumps [rollup](https://github.com/rollup/rollup) from 2.52.8 to 2.53.1.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.52.8...v2.53.1)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-12 09:06:14 +02:00
626936636a build(deps): bump channels from 3.0.3 to 3.0.4 (#1126)
Bumps [channels](https://github.com/django/channels) from 3.0.3 to 3.0.4.
- [Release notes](https://github.com/django/channels/releases)
- [Changelog](https://github.com/django/channels/blob/main/CHANGELOG.txt)
- [Commits](https://github.com/django/channels/compare/3.0.3...3.0.4)

---
updated-dependencies:
- dependency-name: channels
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-12 09:06:03 +02:00
85ec713213 build(deps): bump boto3 from 1.17.108 to 1.17.109 (#1127)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.108 to 1.17.109.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.108...1.17.109)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-12 09:05:54 +02:00
406bbdcfc9 root: fix missing go client in dockerfile
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-11 12:44:26 +02:00
02f87032cc Merge branch 'master' into inbuilt-proxy 2021-07-11 12:41:16 +02:00
b7a929d304 web/flows: update background for 2021.7
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-09 23:12:46 +02:00
3c0cc27ea1 events: fix error when slack notification request failed without a response
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-09 19:52:19 +02:00
ec254d5927 flows: allow variable substitution in flow titles
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-09 19:46:39 +02:00
92ba77e9e5 core: fix error when setting icon/background to url longer than 100 chars
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-09 19:31:32 +02:00
7ddb459030 web: fix error when showing error message of request
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-09 19:06:30 +02:00
076e89b600 build(deps): bump boto3 from 1.17.107 to 1.17.108 (#1122) 2021-07-09 10:05:20 +02:00
ba5fa2a04f build(deps): bump sentry-sdk from 1.2.0 to 1.3.0 (#1121) 2021-07-09 10:05:10 +02:00
90fe1c2ce8 providers/oauth2: allow blank redirect_uris to allow any redirect_uri
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-08 19:28:35 +02:00
85f88e785f build(deps): bump boto3 from 1.17.106 to 1.17.107 (#1120) 2021-07-08 09:50:29 +02:00
a7c4f81275 build(deps): bump rollup from 2.52.7 to 2.52.8 in /web (#1119) 2021-07-08 09:50:21 +02:00
396fbc4a76 build(deps): bump @types/grecaptcha from 3.0.2 to 3.0.3 in /web (#1114)
Bumps [@types/grecaptcha](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/grecaptcha) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/grecaptcha)

---
updated-dependencies:
- dependency-name: "@types/grecaptcha"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-07 10:30:11 +02:00
2dcd0128aa build(deps): bump @types/chart.js from 2.9.33 to 2.9.34 in /web (#1115)
Bumps [@types/chart.js](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/chart.js) from 2.9.33 to 2.9.34.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/chart.js)

---
updated-dependencies:
- dependency-name: "@types/chart.js"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-07 10:29:57 +02:00
e5aa9e0774 build(deps): bump @types/codemirror from 5.60.1 to 5.60.2 in /web (#1116)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 5.60.1 to 5.60.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror)

---
updated-dependencies:
- dependency-name: "@types/codemirror"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-07 10:15:49 +02:00
53d78d561b build(deps): bump sentry-sdk from 1.1.0 to 1.2.0 (#1117)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/1.1.0...1.2.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-07 10:15:37 +02:00
93001d1329 build(deps): bump boto3 from 1.17.105 to 1.17.106 (#1118)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.105 to 1.17.106.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.105...1.17.106)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-07 10:15:26 +02:00
40428f5a82 providers/saml: fix parsing of POST bindings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-06 16:54:58 +02:00
007838fcf2 root: subclass SessionMiddleware to set Secure and SameSite flag depending on context
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-06 14:48:36 +02:00
5e03b27348 website/docs: add note about logging out
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1113
2021-07-06 14:26:11 +02:00
7c51afa36c root: set samesite to None for SAML POST flows
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-06 12:39:51 +02:00
38fd5c5614 build(deps): bump @typescript-eslint/eslint-plugin in /web (#1112)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.28.1 to 4.28.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.28.2/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-06 07:31:10 +00:00
7e3148fab5 build(deps): bump @typescript-eslint/parser in /web (#1111) 2021-07-06 08:58:10 +02:00
948db46406 Merge branch 'master' into inbuilt-proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	internal/constants/constants.go
#	outpost/pkg/version.go
2021-07-05 19:11:26 +02:00
cccddd8c69 ci: re-finalize releases in sentry since sourcemaps are fixed now
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-05 18:30:11 +02:00
adc4cd9c0d release: 2021.6.4 2021-07-05 16:59:29 +02:00
abed254ca1 web/admin: make table dispatch refresh event on refresh button instead of just fetching
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-05 09:48:14 +02:00
edfab0995f build(deps): bump eslint from 7.29.0 to 7.30.0 in /web (#1106) 2021-07-05 09:10:15 +02:00
528dedf99d build(deps): bump chart.js from 3.4.0 to 3.4.1 in /web (#1107) 2021-07-05 09:09:33 +02:00
5d7eec3049 build(deps): bump @types/chart.js from 2.9.32 to 2.9.33 in /web (#1108) 2021-07-05 09:09:24 +02:00
ad44567ebe build(deps): bump packaging from 20.9 to 21.0 (#1109) 2021-07-05 09:09:13 +02:00
ac82002339 build(deps): bump boto3 from 1.17.104 to 1.17.105 (#1110) 2021-07-05 09:08:53 +02:00
df92111296 outposts: update outpost permissions on m2m change
closes #1105

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-04 19:37:12 +02:00
da8417a141 outposts/ldap: re-add old fields for backwards compatibility
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-04 18:10:39 +02:00
7f32355e3e website/docs: update release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-04 13:49:38 +02:00
5afe88a605 outposts: fix empty message when docker outpost controller has changed nothing
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-04 13:48:43 +02:00
320dab3425 core: only show Reset password link when recovery flow is configured
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-04 12:59:41 +02:00
ca44f8bd60 web: log response when >= http 400
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-04 12:39:10 +02:00
5fd408ca82 outposts: fix docker controller not checking ports correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-04 12:32:55 +02:00
becb9e34b5 outposts: fix docker controller not checking env correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 22:17:29 +02:00
4917ab9985 outposts: fix container not being started after creation
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 21:59:47 +02:00
bd92505bc2 core: add notice about duplicate keys
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 21:52:28 +02:00
30033d1f90 g: fix static and media caching not working properly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 21:43:37 +02:00
3e5dfcbd0f website/docs: add release notes for 2021.6.4
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 21:29:52 +02:00
bf0141acc6 crypto: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 19:57:25 +02:00
0c8d513567 stages/user_write: add wrapper for post to user_write
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 19:25:37 +02:00
d07704fdf1 crypto: show both sha1 and sha256 fingerprints
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 19:25:27 +02:00
086a8753c0 flows: handle old cached flow plans better
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 19:22:09 +02:00
ae7a6e2fd6 website/docs: fix gitab saml binding
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 19:02:47 +02:00
6a4ddcaba7 web/admin: don't use form.reset() for ModelForms, reset instance
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 18:26:50 +02:00
2c9b596f01 web/admin: run explicit update after loading instance
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 16:41:42 +02:00
7257108091 sources/oauth: create configuration error event when profile can't be parsed as json
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 16:11:49 +02:00
91f7b289cc web/admin: show oauth2 token revoked status
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 16:04:24 +02:00
77a507d2f8 providers/oauth2: add revoked field, create suspicious event when previous token is used
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 15:59:01 +02:00
3e60e956f4 providers/oauth2: fix CORS headers not being set for unsuccessful requests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 15:49:00 +02:00
84ec70c2a2 providers/oauth2: use self.expires for exp field instead of calculating it again
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 15:32:58 +02:00
72846f0ae1 website/docs: update system requirements
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-03 15:11:40 +02:00
dd53e7e9b1 web/admin: fix ModelForm not re-loading after being reset
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-02 21:21:11 +02:00
9df16a9ae0 website/docs: update gitlab docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-02 21:17:16 +02:00
3dc9e247d5 Merge branch 'master' into inbuilt-proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	internal/constants/constants.go
#	outpost/pkg/version.go
2021-07-02 16:23:30 +02:00
02dd44eeec build(deps): bump rollup from 2.52.4 to 2.52.7 in /web (#1100) 2021-07-02 08:04:31 +02:00
2f78e14381 build(deps): bump channels-redis from 3.2.0 to 3.3.0 (#1101) 2021-07-02 08:04:09 +02:00
ef6f692526 build(deps): bump boto3 from 1.17.102 to 1.17.104 (#1102) 2021-07-02 08:03:58 +02:00
2dd575874b build(deps): bump django from 3.2.4 to 3.2.5 (#1103) 2021-07-02 08:03:48 +02:00
84c2ebabaa build(deps-dev): bump pylint from 2.9.1 to 2.9.3 (#1104) 2021-07-02 08:03:34 +02:00
3e26170f4b providers/oauth2: deepmerge claims
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-01 17:33:46 +02:00
4709dca33c outposts/proxy: always redirect to session-end interface on sign_out
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-01 16:51:36 +02:00
6064a481fb outposts/proxy: set ValidateURL
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-01 15:42:48 +02:00
3979b0bde7 tests/e2e: ensure superuser group is created
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-01 12:16:58 +02:00
4280847bcc tests/e2e: add LDAP bind and search tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-01 11:51:07 +02:00
ade8644da6 outposts/ldap: add support for boolean fields in ldap
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-01 11:51:07 +02:00
3c3fd53999 build(deps): bump typescript from 4.3.4 to 4.3.5 in /web (#1097)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.3.4 to 4.3.5.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.3.4...v4.3.5)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-01 09:42:20 +02:00
7b823f23ae build(deps): bump actions/setup-node from 2.1.5 to 2.2.0 (#1098)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.1.5 to 2.2.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2.1.5...v2.2.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-01 09:42:08 +02:00
a67bea95d4 build(deps-dev): bump pylint from 2.9.0 to 2.9.1 (#1099)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.9.0 to 2.9.1.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.9.0...v2.9.1)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-01 09:41:42 +02:00
775e0ef2fa website/docs: improve docs for restore in k8s
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-30 19:07:11 +02:00
d102c59654 build(deps-dev): bump pylint from 2.8.3 to 2.9.0 (#1095)
* build(deps-dev): bump pylint from 2.8.3 to 2.9.0

Bumps [pylint](https://github.com/PyCQA/pylint) from 2.8.3 to 2.9.0.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.8.3...v2.9.0)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* *: update source for new pylint version

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-30 10:37:28 +02:00
03448a9169 build(deps): bump rollup from 2.52.3 to 2.52.4 in /web (#1094)
Bumps [rollup](https://github.com/rollup/rollup) from 2.52.3 to 2.52.4.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.52.3...v2.52.4)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-30 09:38:53 +02:00
1e6c081e5c website/docs: update forward_auth for nginx config
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-29 20:32:49 +02:00
8b9ce4a745 ci: don't finalise sentry release
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-29 17:08:57 +02:00
2a0bd50e23 outposts: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-29 17:08:12 +02:00
014d93d485 root: fix mismatched version in openapi schema
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-29 16:34:42 +02:00
ff42663d3c root: more code merging
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-29 16:21:00 +02:00
ce49d7ea5b outposts: make managed outposts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-29 16:20:44 +02:00
8429dd19b2 Merge branch 'master' into inbuilt-proxy 2021-06-29 16:20:24 +02:00
1554dc9feb outposts: make outpost managed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-23 21:26:24 +02:00
1005f341e4 Merge branch 'master' into inbuilt-proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	internal/constants/constants.go
#	outpost/pkg/version.go
2021-06-23 20:41:06 +02:00
b98895ac2c root: add more common utils
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-16 17:29:01 +02:00
6dc38b0132 root: start deduplicating code
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-16 12:41:34 +02:00
e154e28611 root: fix build for main server
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-16 12:05:30 +02:00
690b7be1d8 root: initial merging of outpost and main project
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-16 12:02:02 +02:00
185 changed files with 10484 additions and 7718 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2021.6.3
current_version = 2021.7.1-rc1
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
@ -21,14 +21,14 @@ values =
[bumpversion:file:docker-compose.yml]
[bumpversion:file:schema.yml]
[bumpversion:file:.github/workflows/release.yml]
[bumpversion:file:authentik/__init__.py]
[bumpversion:file:internal/constants/constants.go]
[bumpversion:file:outpost/pkg/version.go]
[bumpversion:file:web/src/constants.ts]
[bumpversion:file:website/docs/outposts/manual-deploy-docker-compose.md]

View File

@ -3,3 +3,6 @@ static
htmlcov
*.env.yml
**/node_modules
dist/**
build/**
build_docs/**

View File

@ -9,7 +9,7 @@ updates:
assignees:
- BeryJu
- package-ecosystem: gomod
directory: "/outpost"
directory: "/"
schedule:
interval: daily
time: "04:00"
@ -48,11 +48,3 @@ updates:
open-pull-requests-limit: 10
assignees:
- BeryJu
- package-ecosystem: docker
directory: "/outpost"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
assignees:
- BeryJu

View File

@ -33,14 +33,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik:2021.6.3,
beryju/authentik:2021.7.1-rc1,
beryju/authentik:latest,
ghcr.io/goauthentik/server:2021.6.3,
ghcr.io/goauthentik/server:2021.7.1-rc1,
ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64
context: .
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.6.3', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.1-rc1', 'rc') }}
run: |
docker pull beryju/authentik:latest
docker tag beryju/authentik:latest beryju/authentik:stable
@ -75,14 +75,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-proxy:2021.6.3,
beryju/authentik-proxy:2021.7.1-rc1,
beryju/authentik-proxy:latest,
ghcr.io/goauthentik/proxy:2021.6.3,
ghcr.io/goauthentik/proxy:2021.7.1-rc1,
ghcr.io/goauthentik/proxy:latest
file: outpost/proxy.Dockerfile
file: proxy.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.6.3', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.1-rc1', 'rc') }}
run: |
docker pull beryju/authentik-proxy:latest
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
@ -117,14 +117,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-ldap:2021.6.3,
beryju/authentik-ldap:2021.7.1-rc1,
beryju/authentik-ldap:latest,
ghcr.io/goauthentik/ldap:2021.6.3,
ghcr.io/goauthentik/ldap:2021.7.1-rc1,
ghcr.io/goauthentik/ldap:latest
file: outpost/ldap.Dockerfile
file: ldap.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.6.3', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.1-rc1', 'rc') }}
run: |
docker pull beryju/authentik-ldap:latest
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
@ -157,7 +157,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Setup Node.js environment
uses: actions/setup-node@v2.1.5
uses: actions/setup-node@v2.3.0
with:
node-version: 12.x
- name: Build web api client and web ui
@ -176,6 +176,6 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2021.6.3
version: authentik@2021.7.1-rc1
environment: beryjuorg-prod
sourcemaps: './web/dist'

1
.gitignore vendored
View File

@ -200,3 +200,4 @@ media/
*mmdb
.idea/
api/

View File

@ -10,8 +10,16 @@ RUN pip install pipenv && \
pipenv lock -r > requirements.txt && \
pipenv lock -r --dev-only > requirements-dev.txt
# Stage 2: Build web API
FROM openapitools/openapi-generator-cli as api-builder
# Stage 2: Build website
FROM node as website-builder
COPY ./website /static/
ENV NODE_ENV=production
RUN cd /static && npm i && npm run build-docs-only
# Stage 3: Build web API
FROM openapitools/openapi-generator-cli as web-api-builder
COPY ./schema.yml /local/schema.yml
@ -21,34 +29,52 @@ RUN docker-entrypoint.sh generate \
-o /local/web/api \
--additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0
# Stage 3: Build webui
FROM node as npm-builder
# Stage 3: Generate API Client
FROM openapitools/openapi-generator-cli as go-api-builder
COPY ./schema.yml /local/schema.yml
RUN docker-entrypoint.sh generate \
--git-host goauthentik.io \
--git-repo-id outpost \
--git-user-id api \
-i /local/schema.yml \
-g go \
-o /local/api \
--additional-properties=packageName=api,enumClassPrefix=true,useOneOfDiscriminatorLookup=true && \
rm -f /local/api/go.mod /local/api/go.sum
# Stage 4: Build webui
FROM node as web-builder
COPY ./web /static/
COPY --from=api-builder /local/web/api /static/api
COPY --from=web-api-builder /local/web/api /static/api
ENV NODE_ENV=production
RUN cd /static && npm i && npm run build
# Stage 4: Build go proxy
FROM golang:1.16.5 AS builder
# Stage 5: Build go proxy
FROM golang:1.16.6 AS builder
WORKDIR /work
COPY --from=npm-builder /static/robots.txt /work/web/robots.txt
COPY --from=npm-builder /static/security.txt /work/web/security.txt
COPY --from=npm-builder /static/dist/ /work/web/dist/
COPY --from=npm-builder /static/authentik/ /work/web/authentik/
COPY --from=web-builder /static/robots.txt /work/web/robots.txt
COPY --from=web-builder /static/security.txt /work/web/security.txt
COPY --from=web-builder /static/dist/ /work/web/dist/
COPY --from=web-builder /static/authentik/ /work/web/authentik/
COPY --from=website-builder /static/help/ /work/website/help/
COPY --from=go-api-builder /local/api api
COPY ./cmd /work/cmd
COPY ./web/static.go /work/web/static.go
COPY ./website/static.go /work/website/static.go
COPY ./internal /work/internal
COPY ./go.mod /work/go.mod
COPY ./go.sum /work/go.sum
RUN go build -o /work/authentik ./cmd/server/main.go
# Stage 5: Run
# Stage 6: Run
FROM python:3.9-slim-buster
WORKDIR /

View File

@ -32,7 +32,7 @@ gen-build:
gen-clean:
rm -rf web/api/src/
rm -rf outpost/api/
rm -rf api/
gen-web:
docker run \
@ -55,11 +55,14 @@ gen-outpost:
--git-user-id api \
-i /local/schema.yml \
-g go \
-o /local/outpost/api \
-o /local/api \
--additional-properties=packageName=api,enumClassPrefix=true,useOneOfDiscriminatorLookup=true
rm -f outpost/api/go.mod outpost/api/go.sum
rm -f api/go.mod api/go.sum
gen: gen-build gen-clean gen-web gen-outpost
migrate:
python -m lifecycle.migrate
run:
go run -v cmd/server/main.go

View File

@ -47,6 +47,7 @@ xmlsec = "*"
duo-client = "*"
ua-parser = "*"
deepmerge = "*"
colorama = "*"
[requires]
python_version = "3.9"

371
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "f90d9fb4713eaf9c5ffe6a3858e64843670f79ab5007e7debf914c1f094c8d63"
"sha256": "e4f2e57bd5c709809515ab2b95eb3f5fa337d4a9334f4110a24bf28c3f9d5f8f"
},
"pipfile-spec": 6,
"requires": {
@ -76,11 +76,11 @@
},
"asgiref": {
"hashes": [
"sha256:05914d0fa65a21711e732adc6572edad6c8da5f1435c3f0c060689ced5e85195",
"sha256:d36fa91dd90e3aa3c81a6bd426ccc8fb20bd3d22b0cf14a12800289e9c3e2563"
"sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9",
"sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"
],
"markers": "python_version >= '3.6'",
"version": "==3.4.0"
"version": "==3.4.1"
},
"async-timeout": {
"hashes": [
@ -122,19 +122,19 @@
},
"boto3": {
"hashes": [
"sha256:6300e9ee9a404038113250bd218e2c4827f5e676efb14e77de2ad2dcb67679bc",
"sha256:be4714f0475c1f5183eea09ddbf568ced6fa41b0fc9976f2698b8442e1b17303"
"sha256:13e60f88d13161df951d6e52bd483cdbe1a36a31f818746289d8ba0879465710",
"sha256:3be2f259b279d69495433e3288db3670817fdb1813cfde92abf867bba3ad8148"
],
"index": "pypi",
"version": "==1.17.102"
"version": "==1.18.3"
},
"botocore": {
"hashes": [
"sha256:2f57f7ceed1598d96cc497aeb45317db5d3b21a5aafea4732d0e561d0fc2a8fa",
"sha256:bdf08a4f7f01ead00d386848f089c08270499711447569c18d0db60023619c06"
"sha256:0b6f378c9efbc72eee61aba1e16cab90bde53a37bd2d861f6435552fd7030adf",
"sha256:285ab9459cdd49d4a9322692c6e13772b97af723a03c0eed519b589446491a5b"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.20.102"
"markers": "python_version >= '3.6'",
"version": "==1.21.3"
},
"cachetools": {
"hashes": [
@ -180,73 +180,69 @@
},
"cffi": {
"hashes": [
"sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
"sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373",
"sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69",
"sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f",
"sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
"sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05",
"sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
"sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
"sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0",
"sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
"sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7",
"sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f",
"sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
"sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
"sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76",
"sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
"sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
"sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed",
"sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
"sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
"sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
"sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
"sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
"sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
"sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
"sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55",
"sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
"sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
"sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
"sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
"sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
"sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
"sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
"sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
"sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
"sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
"sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
"sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
"sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
"sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
"sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
"sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
"sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
"sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc",
"sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
"sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
"sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333",
"sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
"sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
"sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d",
"sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771",
"sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872",
"sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c",
"sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc",
"sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762",
"sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202",
"sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5",
"sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548",
"sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a",
"sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f",
"sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20",
"sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218",
"sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c",
"sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e",
"sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56",
"sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224",
"sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a",
"sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2",
"sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a",
"sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819",
"sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346",
"sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b",
"sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e",
"sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534",
"sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb",
"sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0",
"sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156",
"sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd",
"sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87",
"sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc",
"sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195",
"sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33",
"sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f",
"sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d",
"sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd",
"sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728",
"sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7",
"sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca",
"sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99",
"sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf",
"sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e",
"sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c",
"sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5",
"sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"
],
"version": "==1.14.5"
"version": "==1.14.6"
},
"channels": {
"hashes": [
"sha256:056b72e51080a517a0f33a0a30003e03833b551d75394d6636c885d4edb8188f",
"sha256:3f15bdd2138bb4796e76ea588a0a344b12a7964ea9b2e456f992fddb988a4317"
"sha256:0ff0422b4224d10efac76e451575517f155fe7c97d369b5973b116f22eeaf86c",
"sha256:fdd9a94987a23d8d7ebd97498ed8b8cc83163f37e53fc6c85098aba7a3bb8b75"
],
"index": "pypi",
"version": "==3.0.3"
"version": "==3.0.4"
},
"channels-redis": {
"hashes": [
"sha256:18d63f6462a58011740dc8eeb57ea4b31ec220eb551cb71b27de9c6779a549de",
"sha256:2fb31a63b05373f6402da2e6a91a22b9e66eb8b56626c6bfc93e156c734c5ae6"
"sha256:0a18ce279c15ba79b7985bb12b2d6dd0ac8a14e4ad6952681f4422a4cc4a5ea9",
"sha256:1abd5820ff1ed4ac627f8a219ad389e4c87e52e47a230929a7a474e95dd2c6c2"
],
"index": "pypi",
"version": "==3.2.0"
"version": "==3.3.0"
},
"chardet": {
"hashes": [
@ -256,6 +252,14 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0"
},
"charset-normalizer": {
"hashes": [
"sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1",
"sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"
],
"markers": "python_version >= '3'",
"version": "==2.0.3"
},
"click": {
"hashes": [
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
@ -284,6 +288,14 @@
],
"version": "==0.2.0"
},
"colorama": {
"hashes": [
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
],
"index": "pypi",
"version": "==0.4.4"
},
"constantly": {
"hashes": [
"sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35",
@ -342,11 +354,11 @@
},
"django": {
"hashes": [
"sha256:66c9d8db8cc6fe938a28b7887c1596e42d522e27618562517cc8929eb7e7f296",
"sha256:ea735cbbbb3b2fba6d4da4784a0043d84c67c92f1fdf15ad6db69900e792c10f"
"sha256:3da05fea54fdec2315b54a563d5b59f3b4e2b1e69c3a5841dda35019c01855cd",
"sha256:c58b5f19c5ae0afe6d75cbdd7df561e6eb929339985dbbda2565e1cabb19a62e"
],
"index": "pypi",
"version": "==3.2.4"
"version": "==3.2.5"
},
"django-dbbackup": {
"git": "https://github.com/django-dbbackup/django-dbbackup.git",
@ -473,11 +485,11 @@
},
"google-auth": {
"hashes": [
"sha256:b3a67fa9ba5b768861dacf374c2135eb09fa14a0e40c851c3b8ea7abe6fc8fef",
"sha256:e34e5f5de5610b202f9b40ebd9f8b27571d5c5537db9afed3a72b2db5a345039"
"sha256:036dd68c1e8baa422b6b61619b8e02793da2e20f55e69514612de6c080468755",
"sha256:7665c04f2df13cc938dc7d9066cddb1f8af62b038bc8b2306848c1b23121865f"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.32.0"
"version": "==1.33.1"
},
"gunicorn": {
"hashes": [
@ -571,10 +583,10 @@
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
"sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
],
"version": "==2.10"
"version": "==3.2"
},
"incremental": {
"hashes": [
@ -624,14 +636,14 @@
},
"ldap3": {
"hashes": [
"sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
"sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59",
"sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c",
"sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056",
"sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
"sha256:2bc966556fc4d4fa9f445a1c31dc484ee81d44a51ab0e2d0fd05b62cac75daa6",
"sha256:5630d1383e09ba94839e253e013f1aa1a2cf7a547628ba1265cb7b9a844b5687",
"sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70",
"sha256:5ab7febc00689181375de40c396dcad4f2659cd260fc5e94c508b6d77c17e9d5",
"sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f"
],
"index": "pypi",
"version": "==2.9"
"version": "==2.9.1"
},
"lxml": {
"hashes": [
@ -778,11 +790,11 @@
},
"packaging": {
"hashes": [
"sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
"sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
"sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
"sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
],
"index": "pypi",
"version": "==20.9"
"version": "==21.0"
},
"prometheus-client": {
"hashes": [
@ -975,11 +987,11 @@
},
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1"
"version": "==2.8.2"
},
"python-dotenv": {
"hashes": [
@ -1040,11 +1052,11 @@
},
"requests": {
"hashes": [
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.1"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==2.26.0"
},
"requests-oauthlib": {
"hashes": [
@ -1065,18 +1077,19 @@
},
"s3transfer": {
"hashes": [
"sha256:9b3752887a2880690ce628bc263d6d13a3864083aeacff4890c1c9839a5eb0bc",
"sha256:cb022f4b16551edebbb31a377d3f09600dbada7363d8c5db7976e7f47732e1b2"
"sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c",
"sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803"
],
"version": "==0.4.2"
"markers": "python_version >= '3.6'",
"version": "==0.5.0"
},
"sentry-sdk": {
"hashes": [
"sha256:c1227d38dca315ba35182373f129c3e2722e8ed999e52584e6aca7d287870739",
"sha256:c7d380a21281e15be3d9f67a3c4fbb4f800c481d88ff8d8931f39486dd7b4ada"
"sha256:5210a712dd57d88d225c1fc3fe3a3626fee493637bcd54e204826cf04b8d769c",
"sha256:6864dcb6f7dec692635e5518c2a5c80010adf673c70340817f1a1b713d65bb41"
],
"index": "pypi",
"version": "==1.1.0"
"version": "==1.3.0"
},
"service-identity": {
"hashes": [
@ -1206,18 +1219,18 @@
},
"uvloop": {
"hashes": [
"sha256:114543c84e95df1b4ff546e6e3a27521580466a30127f12172a3278172ad68bc",
"sha256:19fa1d56c91341318ac5d417e7b61c56e9a41183946cc70c411341173de02c69",
"sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01",
"sha256:42eda9f525a208fbc4f7cecd00fa15c57cc57646c76632b3ba2fe005004f051d",
"sha256:44cac8575bf168601424302045234d74e3561fbdbac39b2b54cc1d1d00b70760",
"sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c",
"sha256:7ae39b11a5f4cec1432d706c21ecc62f9e04d116883178b09671aa29c46f7a47",
"sha256:90e56f17755e41b425ad19a08c41dc358fa7bf1226c0f8e54d4d02d556f7af7c",
"sha256:b45218c99795803fb8bdbc9435ff7f54e3a591b44cd4c121b02fa83affb61c7c",
"sha256:e5e5f855c9bf483ee6cd1eb9a179b740de80cb0ae2988e3fa22309b78e2ea0e7"
"sha256:0de811931e90ae2da9e19ce70ffad73047ab0c1dba7c6e74f9ae1a3aabeb89bd",
"sha256:1ff05116ede1ebdd81802df339e5b1d4cab1dfbd99295bf27e90b4cec64d70e9",
"sha256:2d8ffe44ae709f839c54bacf14ed283f41bee90430c3b398e521e10f8d117b3a",
"sha256:5cda65fc60a645470b8525ce014516b120b7057b576fa876cdfdd5e60ab1efbb",
"sha256:63a3288abbc9c8ee979d7e34c34e780b2fbab3e7e53d00b6c80271119f277399",
"sha256:7522df4e45e4f25b50adbbbeb5bb9847495c438a628177099d2721f2751ff825",
"sha256:7f4b8a905df909a407c5791fb582f6c03b0d3b491ecdc1cdceaefbc9bf9e08f6",
"sha256:905f0adb0c09c9f44222ee02f6b96fd88b493478fffb7a345287f9444e926030",
"sha256:ae2b325c0f6d748027f7463077e457006b4fdb35a8788f01754aadba825285ee",
"sha256:e71fb9038bfcd7646ca126c5ef19b17e48d4af9e838b2bcfda7a9f55a6552a32"
],
"version": "==0.15.2"
"version": "==0.15.3"
},
"vine": {
"hashes": [
@ -1423,11 +1436,11 @@
},
"astroid": {
"hashes": [
"sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e",
"sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975"
"sha256:6021561b2e87ed6b3c93c2682ac50079c65ab08f1e4e0277ba38f97e0e492185",
"sha256:a670dd7af3fe603f51aa7117462588b7c3bdcd58007edfaee752bf82eceecd28"
],
"markers": "python_version ~= '3.6'",
"version": "==2.5.6"
"version": "==2.6.4"
},
"attrs": {
"hashes": [
@ -1468,13 +1481,13 @@
],
"version": "==2021.5.30"
},
"chardet": {
"charset-normalizer": {
"hashes": [
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
"sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1",
"sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0"
"markers": "python_version >= '3'",
"version": "==2.0.3"
},
"click": {
"hashes": [
@ -1568,10 +1581,10 @@
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
"sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
],
"version": "==2.10"
"version": "==3.2"
},
"iniconfig": {
"hashes": [
@ -1582,11 +1595,11 @@
},
"isort": {
"hashes": [
"sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56",
"sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c"
"sha256:eed17b53c3e7912425579853d078a0832820f023191561fcee9d7cae424e0813",
"sha256:f65ce5bd4cbc6abdfbe29afc2f0245538ab358c14590912df638033f157d555e"
],
"markers": "python_version < '4' and python_full_version >= '3.6.1'",
"version": "==5.9.1"
"markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
"version": "==5.9.2"
},
"lazy-object-proxy": {
"hashes": [
@ -1632,18 +1645,18 @@
},
"packaging": {
"hashes": [
"sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
"sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
"sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
"sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
],
"index": "pypi",
"version": "==20.9"
"version": "==21.0"
},
"pathspec": {
"hashes": [
"sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd",
"sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"
"sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
"sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
],
"version": "==0.8.1"
"version": "==0.9.0"
},
"pbr": {
"hashes": [
@ -1671,11 +1684,11 @@
},
"pylint": {
"hashes": [
"sha256:0a049c5d47b629d9070c3932d13bff482b12119b6a241a93bc460b0be16953c8",
"sha256:792b38ff30903884e4a9eab814ee3523731abd3c463f3ba48d7b627e87013484"
"sha256:2a971129fb2d594068913a7e531d4b6d2785b2a68c6857e2baa40d3214da30f4",
"sha256:a622c4c4c79dc8fe5e784efccacec3afe9d5e5ffab5fda2264fb5afa7c9b5797"
],
"index": "pypi",
"version": "==2.8.3"
"version": "==2.9.4"
},
"pylint-django": {
"hashes": [
@ -1753,57 +1766,57 @@
},
"regex": {
"hashes": [
"sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5",
"sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79",
"sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31",
"sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500",
"sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11",
"sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14",
"sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3",
"sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439",
"sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c",
"sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82",
"sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711",
"sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093",
"sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a",
"sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb",
"sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8",
"sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17",
"sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000",
"sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d",
"sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480",
"sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc",
"sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0",
"sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9",
"sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765",
"sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e",
"sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a",
"sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07",
"sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f",
"sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac",
"sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7",
"sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed",
"sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968",
"sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7",
"sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2",
"sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4",
"sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87",
"sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8",
"sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10",
"sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29",
"sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605",
"sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6",
"sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"
"sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f",
"sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad",
"sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a",
"sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf",
"sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59",
"sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d",
"sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895",
"sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4",
"sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3",
"sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222",
"sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0",
"sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c",
"sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417",
"sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d",
"sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d",
"sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761",
"sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0",
"sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026",
"sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854",
"sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb",
"sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d",
"sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068",
"sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde",
"sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d",
"sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec",
"sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa",
"sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd",
"sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b",
"sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26",
"sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2",
"sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f",
"sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694",
"sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0",
"sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407",
"sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874",
"sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035",
"sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d",
"sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c",
"sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5",
"sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985",
"sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"
],
"version": "==2021.4.4"
"version": "==2021.7.6"
},
"requests": {
"hashes": [
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.1"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==2.26.0"
},
"requests-mock": {
"hashes": [

View File

@ -1,3 +1,3 @@
"""authentik"""
__version__ = "2021.6.3"
__version__ = "2021.7.1-rc1"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -49,7 +49,7 @@ class ConfigView(APIView):
caps.append(Capabilities.CAN_GEO_IP)
if SERVICE_HOST_ENV_NAME in environ:
# Running in k8s, only s3 backup is supported
if CONFIG.y("postgresql.s3_backup"):
if CONFIG.y_bool("postgresql.s3_backup"):
caps.append(Capabilities.CAN_BACKUP)
else:
# Running in compose, backup is always supported

View File

@ -0,0 +1,38 @@
"""Sentry tunnel"""
from json import loads
from django.conf import settings
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.views.generic.base import View
from requests import post
from requests.exceptions import RequestException
from authentik.lib.config import CONFIG
class SentryTunnelView(View):
"""Sentry tunnel, to prevent ad blockers from blocking sentry"""
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Sentry tunnel, to prevent ad blockers from blocking sentry"""
# Only allow usage of this endpoint when error reporting is enabled
if not CONFIG.y_bool("error_reporting.enabled", False):
return HttpResponse(status=400)
# Body is 2 json objects separated by \n
full_body = request.body
header = loads(full_body.splitlines()[0])
# Check that the DSN is what we expect
dsn = header.get("dsn", "")
if dsn != settings.SENTRY_DSN:
return HttpResponse(status=400)
response = post(
"https://sentry.beryju.org/api/8/envelope/",
data=full_body,
headers={"Content-Type": "application/octet-stream"},
)
try:
response.raise_for_status()
except RequestException:
return HttpResponse(status=500)
return HttpResponse(status=response.status_code)

View File

@ -1,5 +1,6 @@
"""api v2 urls"""
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from drf_spectacular.views import SpectacularAPIView
from rest_framework import routers
@ -10,6 +11,7 @@ from authentik.admin.api.tasks import TaskViewSet
from authentik.admin.api.version import VersionView
from authentik.admin.api.workers import WorkerView
from authentik.api.v2.config import ConfigView
from authentik.api.v2.sentry import SentryTunnelView
from authentik.api.views import APIBrowserView
from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
@ -235,6 +237,7 @@ urlpatterns = (
FlowExecutorView.as_view(),
name="flow-executor",
),
path("sentry/", csrf_exempt(SentryTunnelView.as_view()), name="sentry"),
path("schema/", SpectacularAPIView.as_view(), name="schema"),
]
)

View File

@ -1,24 +1,60 @@
"""Groups API Viewset"""
from django.db.models.query import QuerySet
from rest_framework.fields import JSONField
from rest_framework.serializers import ModelSerializer
from rest_framework.fields import BooleanField, CharField, JSONField
from rest_framework.serializers import ListSerializer, ModelSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework_guardian.filters import ObjectPermissionsFilter
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import is_dict
from authentik.core.models import Group
from authentik.core.models import Group, User
class GroupMemberSerializer(ModelSerializer):
"""Stripped down user serializer to show relevant users for groups"""
is_superuser = BooleanField(read_only=True)
avatar = CharField(read_only=True)
attributes = JSONField(validators=[is_dict], required=False)
uid = CharField(read_only=True)
class Meta:
model = User
fields = [
"pk",
"username",
"name",
"is_active",
"last_login",
"is_superuser",
"email",
"avatar",
"attributes",
"uid",
]
class GroupSerializer(ModelSerializer):
"""Group Serializer"""
attributes = JSONField(validators=[is_dict], required=False)
users_obj = ListSerializer(
child=GroupMemberSerializer(), read_only=True, source="users", required=False
)
class Meta:
model = Group
fields = ["pk", "name", "is_superuser", "parent", "users", "attributes"]
fields = [
"pk",
"name",
"is_superuser",
"parent",
"users",
"attributes",
"users_obj",
]
class GroupViewSet(UsedByMixin, ModelViewSet):

View File

@ -12,7 +12,7 @@ from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import Token, TokenIntents
from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents
from authentik.events.models import Event, EventAction
from authentik.managed.api import ManagedSerializer
@ -61,11 +61,19 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
"intent",
"user__username",
"description",
"expires",
"expiring",
]
ordering = ["expires"]
def perform_create(self, serializer: TokenSerializer):
serializer.save(user=self.request.user, intent=TokenIntents.INTENT_API)
serializer.save(
user=self.request.user,
intent=TokenIntents.INTENT_API,
expiring=self.request.user.attributes.get(
USER_ATTRIBUTE_TOKEN_EXPIRING, True
),
)
@permission_required("authentik_core.view_token_key")
@extend_schema(

View File

@ -2,12 +2,11 @@
from json import loads
from django.db.models.query import QuerySet
from django.http.response import Http404
from django.urls import reverse_lazy
from django.utils.http import urlencode
from django_filters.filters import BooleanFilter, CharFilter
from django_filters.filterset import FilterSet
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_field
from drf_spectacular.utils import extend_schema, extend_schema_field
from guardian.utils import get_anonymous_user
from rest_framework.decorators import action
from rest_framework.fields import CharField, JSONField, SerializerMethodField
@ -173,7 +172,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
@extend_schema(
responses={
"200": LinkSerializer(many=False),
"404": OpenApiResponse(description="No recovery flow found."),
"404": LinkSerializer(many=False),
},
)
@action(detail=True, pagination_class=None, filter_backends=[])
@ -184,7 +183,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
# Check that there is a recovery flow, if not return an error
flow = tenant.flow_recovery
if not flow:
raise Http404
return Response({"link": ""}, status=404)
user: User = self.get_object()
token, __ = Token.objects.get_or_create(
identifier=f"{user.uid}-password-reset",

View File

@ -14,7 +14,9 @@ def is_dict(value: Any):
"""Ensure a value is a dictionary, useful for JSONFields"""
if isinstance(value, dict):
return
raise ValidationError("Value must be a dictionary.")
raise ValidationError(
"Value must be a dictionary, and not have any duplicate keys."
)
class PassiveSerializer(Serializer):

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.5 on 2021-07-09 17:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0025_alter_application_meta_icon"),
]
operations = [
migrations.AlterField(
model_name="application",
name="meta_icon",
field=models.FileField(
default=None, max_length=500, null=True, upload_to="application-icons/"
),
),
]

View File

@ -5,14 +5,13 @@ from typing import Any, Optional, Type
from urllib.parse import urlencode
from uuid import uuid4
import django.db.models.options as options
from deepmerge import always_merger
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as DjangoUserManager
from django.core import validators
from django.db import models
from django.db.models import Q, QuerySet
from django.db.models import Q, QuerySet, options
from django.http import HttpRequest
from django.templatetags.static import static
from django.utils.functional import cached_property
@ -38,6 +37,8 @@ LOGGER = get_logger()
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account"
USER_ATTRIBUTE_SOURCES = "goauthentik.io/user/sources"
USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec
USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips"
GRAVATAR_URL = "https://secure.gravatar.com"
DEFAULT_AVATAR = static("dist/assets/images/user_default.png")
@ -229,7 +230,10 @@ class Application(PolicyBindingModel):
)
# For template applications, this can be set to /static/authentik/applications/*
meta_icon = models.FileField(
upload_to="application-icons/", default=None, null=True
upload_to="application-icons/",
default=None,
null=True,
max_length=500,
)
meta_description = models.TextField(default="", blank=True)
meta_publisher = models.TextField(default="", blank=True)
@ -378,6 +382,13 @@ class ExpiringModel(models.Model):
expires = models.DateTimeField(default=default_token_duration)
expiring = models.BooleanField(default=True)
def expire_action(self, *args, **kwargs):
"""Handler which is called when this object is expired. By
default the object is deleted. This is less efficient compared
to bulk deleting objects, but classes like Token() need to change
values instead of being deleted."""
return self.delete(*args, **kwargs)
@classmethod
def filter_not_expired(cls, **kwargs) -> QuerySet:
"""Filer for tokens which are not expired yet or are not expiring,
@ -422,6 +433,18 @@ class Token(ManagedModel, ExpiringModel):
user = models.ForeignKey("User", on_delete=models.CASCADE, related_name="+")
description = models.TextField(default="", blank=True)
def expire_action(self, *args, **kwargs):
"""Handler which is called when this object is expired."""
from authentik.events.models import Event, EventAction
self.key = default_token_key()
self.save(*args, **kwargs)
Event.new(
action=EventAction.SECRET_ROTATE,
token=self,
message=f"Token {self.identifier}'s secret was rotated.",
).save()
def __str__(self):
description = f"{self.identifier}"
if self.expiring:

View File

@ -26,14 +26,16 @@ def clean_expired_models(self: MonitoredTask):
messages = []
for cls in ExpiringModel.__subclasses__():
cls: ExpiringModel
amount, _ = (
objects = (
cls.objects.all()
.exclude(expiring=False)
.exclude(expiring=True, expires__gt=now())
.delete()
)
LOGGER.debug("Deleted expired models", model=cls, amount=amount)
messages.append(f"Deleted {amount} expired {cls._meta.verbose_name_plural}")
for obj in objects:
obj.expire_action()
amount = objects.count()
LOGGER.debug("Expired models", model=cls, amount=amount)
messages.append(f"Expired {amount} {cls._meta.verbose_name_plural}")
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages))

View File

@ -1,18 +0,0 @@
"""authentik core task tests"""
from django.test import TestCase
from django.utils.timezone import now
from guardian.shortcuts import get_anonymous_user
from authentik.core.models import Token
from authentik.core.tasks import clean_expired_models
class TestTasks(TestCase):
"""Test Tasks"""
def test_token_cleanup(self):
"""Test Token cleanup task"""
Token.objects.create(expires=now(), user=get_anonymous_user())
self.assertEqual(Token.objects.all().count(), 1)
clean_expired_models.delay().get()
self.assertEqual(Token.objects.all().count(), 0)

View File

@ -1,8 +1,16 @@
"""Test token API"""
from django.urls.base import reverse
from django.utils.timezone import now
from guardian.shortcuts import get_anonymous_user
from rest_framework.test import APITestCase
from authentik.core.models import Token, TokenIntents, User
from authentik.core.models import (
USER_ATTRIBUTE_TOKEN_EXPIRING,
Token,
TokenIntents,
User,
)
from authentik.core.tasks import clean_expired_models
class TestTokenAPI(APITestCase):
@ -22,3 +30,25 @@ class TestTokenAPI(APITestCase):
token = Token.objects.get(identifier="test-token")
self.assertEqual(token.user, self.user)
self.assertEqual(token.intent, TokenIntents.INTENT_API)
self.assertEqual(token.expiring, True)
def test_token_create_non_expiring(self):
"""Test token creation endpoint"""
self.user.attributes[USER_ATTRIBUTE_TOKEN_EXPIRING] = False
self.user.save()
response = self.client.post(
reverse("authentik_api:token-list"), {"identifier": "test-token"}
)
self.assertEqual(response.status_code, 201)
token = Token.objects.get(identifier="test-token")
self.assertEqual(token.user, self.user)
self.assertEqual(token.intent, TokenIntents.INTENT_API)
self.assertEqual(token.expiring, False)
def test_token_expire(self):
"""Test Token expire task"""
token: Token = Token.objects.create(expires=now(), user=get_anonymous_user())
key = token.key
clean_expired_models.delay().get()
token.refresh_from_db()
self.assertNotEqual(key, token.key)

View File

@ -97,7 +97,8 @@ class CertificateKeyPairSerializer(ModelSerializer):
fields = [
"pk",
"name",
"fingerprint",
"fingerprint_sha256",
"fingerprint_sha1",
"certificate_data",
"key_data",
"cert_expiry",

View File

@ -16,11 +16,6 @@ from authentik.crypto.models import CertificateKeyPair
class CertificateBuilder:
"""Build self-signed certificates"""
__public_key = None
__private_key = None
__builder = None
__certificate = None
common_name: str
def __init__(self):

View File

@ -68,12 +68,19 @@ class CertificateKeyPair(CreatedUpdatedModel):
return self._private_key
@property
def fingerprint(self) -> str:
def fingerprint_sha256(self) -> str:
"""Get SHA256 Fingerprint of certificate_data"""
return hexlify(self.certificate.fingerprint(hashes.SHA256()), ":").decode(
"utf-8"
)
@property
def fingerprint_sha1(self) -> str:
"""Get SHA1 Fingerprint of certificate_data"""
return hexlify(
self.certificate.fingerprint(hashes.SHA1()), ":" # nosec
).decode("utf-8")
@property
def kid(self):
"""Get Key ID used for JWKS"""

View File

@ -46,7 +46,7 @@ class NotificationTransportTestSerializer(Serializer):
messages = ListField(child=CharField())
def create(self, request: Request) -> Response:
def create(self, validated_data: Request) -> Response:
raise NotImplementedError
def update(self, request: Request) -> Response:

View File

@ -27,10 +27,9 @@ class GeoIPDict(TypedDict):
class GeoIPReader:
"""Slim wrapper around GeoIP API"""
__reader: Optional[Reader] = None
__last_mtime: float = 0.0
def __init__(self):
self.__reader: Optional[Reader] = None
self.__last_mtime: float = 0.0
self.__open()
def __open(self):

View File

@ -3,6 +3,7 @@ from functools import partial
from typing import Callable
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.db.models import Model
from django.db.models.signals import post_save, pre_delete
from django.http import HttpRequest, HttpResponse
@ -63,7 +64,15 @@ class AuditMiddleware:
if settings.DEBUG:
return
if before_send({}, {"exc_info": (None, exception, None)}) is not None:
# Special case for SuspiciousOperation, we have a special event action for that
if isinstance(exception, SuspiciousOperation):
thread = EventNewThread(
EventAction.SUSPICIOUS_REQUEST,
request,
message=str(exception),
)
thread.run()
elif before_send({}, {"exc_info": (None, exception, None)}) is not None:
thread = EventNewThread(
EventAction.SYSTEM_EXCEPTION,
request,

View File

@ -0,0 +1,47 @@
# Generated by Django 3.2.5 on 2021-07-14 19:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0016_add_tenant"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("secret_rotate", "Secret Rotate"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -62,6 +62,7 @@ class EventAction(models.TextChoices):
PASSWORD_SET = "password_set" # noqa # nosec
SECRET_VIEW = "secret_view" # noqa # nosec
SECRET_ROTATE = "secret_rotate" # noqa # nosec
INVITE_USED = "invitation_used"
@ -313,7 +314,8 @@ class NotificationTransport(models.Model):
response = post(self.webhook_url, json=body)
response.raise_for_status()
except RequestException as exc:
raise NotificationTransportError(exc.response.text) from exc
text = exc.response.text if exc.response else str(exc)
raise NotificationTransportError(text) from exc
return [
response.status_code,
response.text,

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.4 on 2021-07-03 13:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0021_flowstagebinding_invalid_response_action"),
]
operations = [
migrations.AlterField(
model_name="flowstagebinding",
name="invalid_response_action",
field=models.TextField(
choices=[
("retry", "Retry"),
("restart", "Restart"),
("restart_with_context", "Restart With Context"),
],
default="retry",
help_text="Configure how the flow executor should handle an invalid response to a challenge. RETRY returns the error message and a similar challenge to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT restarts the flow while keeping the current context.",
),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.5 on 2021-07-09 17:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0022_alter_flowstagebinding_invalid_response_action"),
]
operations = [
migrations.AlterField(
model_name="flow",
name="background",
field=models.FileField(
default=None,
help_text="Background shown during execution",
max_length=500,
null=True,
upload_to="flow-backgrounds/",
),
),
]

View File

@ -121,6 +121,7 @@ class Flow(SerializerModel, PolicyBindingModel):
default=None,
null=True,
help_text=_("Background shown during execution"),
max_length=500,
)
compatibility_mode = models.BooleanField(

View File

@ -17,7 +17,7 @@ from authentik.flows.challenge import (
WithUserInfoChallenge,
)
from authentik.flows.models import InvalidResponseAction
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_PENDING_USER
from authentik.flows.views import FlowExecutorView
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
@ -102,12 +102,18 @@ class ChallengeStageView(StageView):
return self.challenge_invalid(challenge)
return self.challenge_valid(challenge)
def format_title(self) -> str:
"""Allow usage of placeholder in flow title."""
return self.executor.flow.title % {
"app": self.executor.plan.context.get(PLAN_CONTEXT_APPLICATION, "")
}
def _get_challenge(self, *args, **kwargs) -> Challenge:
challenge = self.get_challenge(*args, **kwargs)
if "flow_info" not in challenge.initial_data:
flow_info = ContextualFlowInfo(
data={
"title": self.executor.flow.title,
"title": self.format_title(),
"background": self.executor.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
}

View File

@ -40,15 +40,11 @@ def transaction_rollback():
class FlowImporter:
"""Import Flow from json"""
__import: FlowBundle
__pk_map: dict[Any, Model]
logger: BoundLogger
def __init__(self, json_input: str):
self.__pk_map: dict[Any, Model] = {}
self.logger = get_logger()
self.__pk_map = {}
import_dict = loads(json_input)
try:
self.__import = from_dict(FlowBundle, import_dict)

View File

@ -134,7 +134,7 @@ class FlowExecutorView(APIView):
message = exc.__doc__ if exc.__doc__ else str(exc)
return self.stage_invalid(error_message=message)
# pylint: disable=unused-argument
# pylint: disable=unused-argument, too-many-return-statements
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
# Early check if theres an active Plan for the current session
if SESSION_KEY_PLAN in self.request.session:
@ -167,7 +167,18 @@ class FlowExecutorView(APIView):
request.session[SESSION_KEY_GET] = QueryDict(request.GET.get("query", ""))
# We don't save the Plan after getting the next stage
# as it hasn't been successfully passed yet
next_binding = self.plan.next(self.request)
try:
# This is the first time we actually access any attribute on the selected plan
# if the cached plan is from an older version, it might have different attributes
# in which case we just delete the plan and invalidate everything
next_binding = self.plan.next(self.request)
except Exception as exc: # pylint: disable=broad-except
self._logger.warning(
"f(exec): found incompatible flow plan, invalidating run", exc=exc
)
keys = cache.keys("flow_*")
cache.delete_many(keys)
return self.stage_invalid()
if not next_binding:
self._logger.debug("f(exec): no more stages, flow is done.")
return self._flow_done()

View File

@ -26,10 +26,9 @@ class ConfigLoader:
loaded_file = []
__config = {}
def __init__(self):
super().__init__()
self.__config = {}
base_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), "../.."))
for path in SEARCH_PATHS:
# Check if path is relative, and if so join with base_dir

View File

@ -13,7 +13,10 @@ web:
redis:
host: localhost
port: 6379
password: ''
tls: false
tls_reqs: "none"
cache_db: 0
message_queue_db: 1
ws_db: 2

View File

@ -2,10 +2,12 @@
from typing import Any, Optional
from django.http import HttpRequest
from structlog.stdlib import get_logger
OUTPOST_REMOTE_IP_HEADER = "HTTP_X_AUTHENTIK_REMOTE_IP"
USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips"
OUTPOST_TOKEN_HEADER = "HTTP_X_AUTHENTIK_OUTPOST_TOKEN" # nosec
DEFAULT_IP = "255.255.255.255"
LOGGER = get_logger()
def _get_client_ip_from_meta(meta: dict[str, Any]) -> str:
@ -27,13 +29,25 @@ def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
"""Get the actual remote IP when set by an outpost. Only
allowed when the request is authenticated, by a user with USER_ATTRIBUTE_CAN_OVERRIDE_IP set
to outpost"""
if not hasattr(request, "user"):
from authentik.core.models import (
USER_ATTRIBUTE_CAN_OVERRIDE_IP,
Token,
TokenIntents,
)
if (
OUTPOST_REMOTE_IP_HEADER not in request.META
or OUTPOST_TOKEN_HEADER not in request.META
):
return None
if not request.user.is_authenticated:
tokens = Token.filter_not_expired(
key=request.META.get(OUTPOST_TOKEN_HEADER), intent=TokenIntents.INTENT_API
)
if not tokens.exists():
LOGGER.warning("Attempted remote-ip override without token")
return None
if OUTPOST_REMOTE_IP_HEADER not in request.META:
return None
if request.user.group_attributes().get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False):
user = tokens.first().user
if user.group_attributes().get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False):
return None
return request.META[OUTPOST_REMOTE_IP_HEADER]

View File

@ -51,7 +51,7 @@ class OutpostSerializer(ModelSerializer):
raise ValidationError(
(
f"Outpost type {self.initial_data['type']} can't be used with "
f"{type(provider)} providers."
f"{provider.__class__.__name__} providers."
)
)
return providers
@ -77,6 +77,7 @@ class OutpostSerializer(ModelSerializer):
"service_connection_obj",
"token_identifier",
"config",
"managed",
]
extra_kwargs = {"type": {"required": True}}

View File

@ -69,7 +69,7 @@ class OutpostConsumer(AuthJsonConsumer):
self.last_uid = self.channel_name
# pylint: disable=unused-argument
def disconnect(self, close_code):
def disconnect(self, code):
if self.outpost and self.last_uid:
state = OutpostState.for_instance_uid(self.outpost, self.last_uid)
if self.channel_name in state.channel_ids:

View File

@ -36,8 +36,10 @@ class DockerController(BaseController):
def _get_env(self) -> dict[str, str]:
return {
"AUTHENTIK_HOST": self.outpost.config.authentik_host,
"AUTHENTIK_INSECURE": str(self.outpost.config.authentik_host_insecure),
"AUTHENTIK_HOST": self.outpost.config.authentik_host.lower(),
"AUTHENTIK_INSECURE": str(
self.outpost.config.authentik_host_insecure
).lower(),
"AUTHENTIK_TOKEN": self.outpost.token.key,
}
@ -45,11 +47,10 @@ class DockerController(BaseController):
"""Check if container's env is equal to what we would set. Return true if container needs
to be rebuilt."""
should_be = self._get_env()
container_env = container.attrs.get("Config", {}).get("Env", {})
container_env = container.attrs.get("Config", {}).get("Env", [])
for key, expected_value in should_be.items():
if key not in container_env:
continue
if container_env[key] != expected_value:
entry = f"{key.upper()}={expected_value}"
if entry not in container_env:
return True
return False
@ -62,14 +63,17 @@ class DockerController(BaseController):
# When the container isn't running, the API doesn't report any port mappings
if container.status != "running":
return False
# {'6379/tcp': [{'HostIp': '127.0.0.1', 'HostPort': '6379'}]}
# {'3389/tcp': [
# {'HostIp': '0.0.0.0', 'HostPort': '389'},
# {'HostIp': '::', 'HostPort': '389'}
# ]}
for port in self.deployment_ports:
key = f"{port.inner_port or port.port}/{port.protocol.lower()}"
if key not in container.ports:
return True
host_matching = False
for host_port in container.ports[key]:
host_matching = host_port.get("HostPort") == port.port
host_matching = host_port.get("HostPort") == str(port.port)
if not host_matching:
return True
return False
@ -79,7 +83,7 @@ class DockerController(BaseController):
try:
return self.client.containers.get(container_name), False
except NotFound:
self.logger.info("Container does not exist, creating")
self.logger.info("(Re-)creating container...")
image_name = self.get_container_image()
self.client.images.pull(image_name)
container_args = {
@ -107,6 +111,7 @@ class DockerController(BaseController):
try:
container, has_been_created = self._get_container()
if has_been_created:
container.start()
return None
# Check if the container is out of date, delete it and retry
if len(container.image.tags) > 0:
@ -164,6 +169,7 @@ class DockerController(BaseController):
self.logger.info("Container is not running, restarting...")
container.start()
return None
self.logger.info("Container is running")
return None
except DockerException as exc:
raise ControllerException(str(exc)) from exc

View File

@ -0,0 +1,18 @@
"""Outpost managed objects"""
from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.outposts.models import Outpost, OutpostType
class OutpostManager(ObjectManager):
"""Outpost managed objects"""
def reconcile(self):
return [
EnsureExists(
Outpost,
"goauthentik.io/outposts/inbuilt",
name="authentik Bundeled Outpost",
object_field="name",
type=OutpostType.PROXY,
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.4 on 2021-06-23 19:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_outposts", "0016_alter_outpost_type"),
]
operations = [
migrations.AddField(
model_name="outpost",
name="managed",
field=models.TextField(
default=None,
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
null=True,
unique=True,
verbose_name="Managed by authentik",
),
),
]

View File

@ -28,12 +28,19 @@ from structlog.stdlib import get_logger
from urllib3.exceptions import HTTPError
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik.core.models import USER_ATTRIBUTE_SA, Provider, Token, TokenIntents, User
from authentik.core.models import (
USER_ATTRIBUTE_CAN_OVERRIDE_IP,
USER_ATTRIBUTE_SA,
Provider,
Token,
TokenIntents,
User,
)
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.config import CONFIG
from authentik.lib.models import InheritanceForeignKey
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.http import USER_ATTRIBUTE_CAN_OVERRIDE_IP
from authentik.managed.models import ManagedModel
from authentik.outposts.controllers.k8s.utils import get_namespace
from authentik.outposts.docker_tls import DockerInlineTLS
@ -281,7 +288,7 @@ class KubernetesServiceConnection(OutpostServiceConnection):
verbose_name_plural = _("Kubernetes Service-Connections")
class Outpost(models.Model):
class Outpost(ManagedModel):
"""Outpost instance which manages a service user and token"""
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)

View File

@ -9,7 +9,7 @@ CELERY_BEAT_SCHEDULE = {
},
"outposts_service_connection_check": {
"task": "authentik.outposts.tasks.outpost_service_connection_monitor",
"schedule": crontab(minute="*/60"),
"schedule": crontab(minute="*/5"),
"options": {"queue": "authentik_scheduled"},
},
"outpost_token_ensurer": {

View File

@ -1,7 +1,7 @@
"""authentik outpost signals"""
from django.core.cache import cache
from django.db.models import Model
from django.db.models.signals import post_save, pre_delete, pre_save
from django.db.models.signals import m2m_changed, post_save, pre_delete, pre_save
from django.dispatch import receiver
from structlog.stdlib import get_logger
@ -46,6 +46,14 @@ def pre_save_outpost(sender, instance: Outpost, **_):
outpost_controller.delay(instance.pk.hex, action="down", from_cache=True)
@receiver(m2m_changed, sender=Outpost.providers.through)
# pylint: disable=unused-argument
def m2m_changed_update(sender, instance: Model, action: str, **_):
"""Update outpost on m2m change, when providers are added or removed"""
if action in ["post_add", "post_remove", "post_clear"]:
outpost_post_save.delay(class_to_path(instance.__class__), instance.pk)
@receiver(post_save)
# pylint: disable=unused-argument
def post_save_update(sender, instance: Model, **_):

View File

@ -82,13 +82,13 @@ class PolicyBindingSerializer(ModelSerializer):
"timeout",
]
def validate(self, data: OrderedDict) -> OrderedDict:
def validate(self, attrs: OrderedDict) -> OrderedDict:
"""Check that either policy, group or user is set."""
count = sum(
[
bool(data.get("policy", None)),
bool(data.get("group", None)),
bool(data.get("user", None)),
bool(attrs.get("policy", None)),
bool(attrs.get("group", None)),
bool(attrs.get("user", None)),
]
)
invalid = count > 1
@ -97,7 +97,7 @@ class PolicyBindingSerializer(ModelSerializer):
raise ValidationError("Only one of 'policy', 'group' or 'user' can be set.")
if empty:
raise ValidationError("One of 'policy', 'group' or 'user' must be set.")
return data
return attrs
class PolicyBindingViewSet(UsedByMixin, ModelViewSet):

View File

@ -62,12 +62,6 @@ class PolicyEngine:
# Allow objects with no policies attached to pass
empty_result: bool
__pbm: PolicyBindingModel
__cached_policies: list[PolicyResult]
__processes: list[PolicyProcessInfo]
__expected_result_count: int
def __init__(
self, pbm: PolicyBindingModel, user: User, request: HttpRequest = None
):
@ -83,8 +77,8 @@ class PolicyEngine:
self.request.obj = pbm
if request:
self.request.set_http_request(request)
self.__cached_policies = []
self.__processes = []
self.__cached_policies: list[PolicyResult] = []
self.__processes: list[PolicyProcessInfo] = []
self.use_cache = True
self.__expected_result_count = 0

View File

@ -0,0 +1,49 @@
# Generated by Django 3.2.5 on 2021-07-14 19:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0017_alter_eventmatcherpolicy_action"),
]
operations = [
migrations.AlterField(
model_name="eventmatcherpolicy",
name="action",
field=models.TextField(
blank=True,
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("secret_rotate", "Secret Rotate"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
],
help_text="Match created events with this action type. When left empty, all action types will be matched.",
),
),
]

View File

@ -21,12 +21,15 @@ def update_score(request: HttpRequest, username: str, amount: int):
"""Update score for IP and User"""
remote_ip = get_client_ip(request)
# We only update the cache here, as its faster than writing to the DB
cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0, CACHE_TIMEOUT)
cache.incr(CACHE_KEY_IP_PREFIX + remote_ip, amount)
try:
# We only update the cache here, as its faster than writing to the DB
cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0, CACHE_TIMEOUT)
cache.incr(CACHE_KEY_IP_PREFIX + remote_ip, amount)
cache.get_or_set(CACHE_KEY_USER_PREFIX + username, 0, CACHE_TIMEOUT)
cache.incr(CACHE_KEY_USER_PREFIX + username, amount)
cache.get_or_set(CACHE_KEY_USER_PREFIX + username, 0, CACHE_TIMEOUT)
cache.incr(CACHE_KEY_USER_PREFIX + username, amount)
except ValueError as exc:
LOGGER.warning("failed to set reputation", exc=exc)
LOGGER.debug("Updated score", amount=amount, for_user=username, for_ip=remote_ip)

View File

@ -17,6 +17,10 @@ class LDAPProviderSerializer(ProviderSerializer):
fields = ProviderSerializer.Meta.fields + [
"base_dn",
"search_group",
"certificate",
"tls_server_name",
"uid_start_number",
"gid_start_number",
]
@ -44,6 +48,10 @@ class LDAPOutpostConfigSerializer(ModelSerializer):
"bind_flow_slug",
"application_slug",
"search_group",
"certificate",
"tls_server_name",
"uid_start_number",
"gid_start_number",
]

View File

@ -11,4 +11,5 @@ class LDAPDockerController(DockerController):
super().__init__(outpost, connection)
self.deployment_ports = [
DeploymentPort(389, "ldap", "tcp", 3389),
DeploymentPort(636, "ldaps", "tcp", 6636),
]

View File

@ -11,4 +11,5 @@ class LDAPKubernetesController(KubernetesController):
super().__init__(outpost, connection)
self.deployment_ports = [
DeploymentPort(389, "ldap", "tcp", 3389),
DeploymentPort(636, "ldaps", "tcp", 6636),
]

View File

@ -0,0 +1,30 @@
# Generated by Django 3.2.5 on 2021-07-13 11:38
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_crypto", "0002_create_self_signed_kp"),
("authentik_providers_ldap", "0002_ldapprovider_search_group"),
]
operations = [
migrations.AddField(
model_name="ldapprovider",
name="certificate",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_crypto.certificatekeypair",
),
),
migrations.AddField(
model_name="ldapprovider",
name="tls_server_name",
field=models.TextField(blank=True, default=""),
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 3.2.5 on 2021-07-13 21:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_ldap", "0003_auto_20210713_1138"),
]
operations = [
migrations.AddField(
model_name="ldapprovider",
name="gid_start_number",
field=models.IntegerField(
default=4000,
help_text="The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber",
),
),
migrations.AddField(
model_name="ldapprovider",
name="uid_start_number",
field=models.IntegerField(
default=2000,
help_text="The start for uidNumbers, this number is added to the user.Pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber",
),
),
]

View File

@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from authentik.core.models import Group, Provider
from authentik.crypto.models import CertificateKeyPair
from authentik.outposts.models import OutpostModel
@ -28,6 +29,36 @@ class LDAPProvider(OutpostModel, Provider):
),
)
tls_server_name = models.TextField(
default="",
blank=True,
)
certificate = models.ForeignKey(
CertificateKeyPair,
on_delete=models.SET_NULL,
null=True,
blank=True,
)
uid_start_number = models.IntegerField(
default=2000,
help_text=_(
"The start for uidNumbers, this number is added to the user.Pk to make sure that the "
"numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't "
"collide with local users uidNumber"
),
)
gid_start_number = models.IntegerField(
default=4000,
help_text=_(
"The start for gidNumbers, this number is added to a number generated from the "
"group.Pk to make sure that the numbers aren't too low for POSIX groups. Default "
"is 4000 to ensure that we don't collide with local groups or users "
"primary groups gidNumber"
),
)
@property
def launch_url(self) -> Optional[str]:
"""LDAP never has a launch URL"""

View File

@ -51,6 +51,7 @@ class RefreshTokenModelSerializer(ExpiringBaseGrantModelSerializer):
"expires",
"scope",
"id_token",
"revoked",
]
depth = 2

View File

@ -109,6 +109,7 @@ class Migration(migrations.Migration):
"redirect_uris",
models.TextField(
default="",
blank=True,
help_text="Enter each URI on a new line.",
verbose_name="Redirect URIs",
),

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.4 on 2021-07-03 13:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_oauth2", "0014_alter_oauth2provider_rsa_key"),
]
operations = [
migrations.AddField(
model_name="authorizationcode",
name="revoked",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="refreshtoken",
name="revoked",
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.5 on 2021-07-20 22:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_oauth2", "0015_auto_20210703_1313"),
]
operations = [
migrations.AlterField(
model_name="authorizationcode",
name="nonce",
field=models.TextField(default=None, null=True, verbose_name="Nonce"),
),
]

View File

@ -158,6 +158,7 @@ class OAuth2Provider(Provider):
)
redirect_uris = models.TextField(
default="",
blank=True,
verbose_name=_("Redirect URIs"),
help_text=_("Enter each URI on a new line."),
)
@ -278,7 +279,7 @@ class OAuth2Provider(Provider):
"""Guess launch_url based on first redirect_uri"""
if self.redirect_uris == "":
return None
main_url = self.redirect_uris.split("\n")[0]
main_url = self.redirect_uris.split("\n", maxsplit=1)[0]
launch_url = urlparse(main_url)
return main_url.replace(launch_url.path, "")
@ -318,6 +319,7 @@ class BaseGrantModel(models.Model):
provider = models.ForeignKey(OAuth2Provider, on_delete=models.CASCADE)
user = models.ForeignKey(User, verbose_name=_("User"), on_delete=models.CASCADE)
_scope = models.TextField(default="", verbose_name=_("Scopes"))
revoked = models.BooleanField(default=False)
@property
def scope(self) -> list[str]:
@ -336,7 +338,7 @@ class AuthorizationCode(ExpiringModel, BaseGrantModel):
"""OAuth2 Authorization Code"""
code = models.CharField(max_length=255, unique=True, verbose_name=_("Code"))
nonce = models.TextField(blank=True, default="", verbose_name=_("Nonce"))
nonce = models.TextField(null=True, default=None, verbose_name=_("Nonce"))
is_open_id = models.BooleanField(
default=False, verbose_name=_("Is Authentication?")
)
@ -473,9 +475,7 @@ class RefreshToken(ExpiringModel, BaseGrantModel):
# Convert datetimes into timestamps.
now = int(time.time())
iat_time = now
exp_time = int(
now + timedelta_from_string(self.provider.token_validity).total_seconds()
)
exp_time = int(dateformat.format(self.expires, "U"))
# We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time
auth_events = Event.objects.filter(
action=EventAction.LOGIN, user=get_user(user)

View File

@ -67,7 +67,7 @@ class TestAuthorize(OAuthTestCase):
)
OAuthAuthorizationParams.from_request(request)
def test_redirect_uri(self):
def test_invalid_redirect_uri(self):
"""test missing/invalid redirect URI"""
OAuth2Provider.objects.create(
name="test",
@ -91,6 +91,28 @@ class TestAuthorize(OAuthTestCase):
)
OAuthAuthorizationParams.from_request(request)
def test_empty_redirect_uri(self):
"""test empty redirect URI (configure in provider)"""
OAuth2Provider.objects.create(
name="test",
client_id="test",
authorization_flow=Flow.objects.first(),
)
with self.assertRaises(RedirectUriError):
request = self.factory.get(
"/", data={"response_type": "code", "client_id": "test"}
)
OAuthAuthorizationParams.from_request(request)
request = self.factory.get(
"/",
data={
"response_type": "code",
"client_id": "test",
"redirect_uri": "http://localhost",
},
)
OAuthAuthorizationParams.from_request(request)
def test_response_type(self):
"""test response_type"""
OAuth2Provider.objects.create(

View File

@ -6,6 +6,8 @@ from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application, User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.flows.models import Flow
from authentik.providers.oauth2.constants import (
GRANT_TYPE_AUTHORIZATION_CODE,
@ -39,7 +41,8 @@ class TestToken(OAuthTestCase):
client_id=generate_client_id(),
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
redirect_uris="http://testserver",
rsa_key=CertificateKeyPair.objects.first(),
)
header = b64encode(
f"{provider.client_id}:{provider.client_secret}".encode()
@ -53,11 +56,13 @@ class TestToken(OAuthTestCase):
data={
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
"code": code.code,
"redirect_uri": "http://local.invalid",
"redirect_uri": "http://testserver",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
params = TokenParams.from_request(request)
params = TokenParams.parse(
request, provider, provider.client_id, provider.client_secret
)
self.assertEqual(params.provider, provider)
def test_request_refresh_token(self):
@ -68,6 +73,7 @@ class TestToken(OAuthTestCase):
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
rsa_key=CertificateKeyPair.objects.first(),
)
header = b64encode(
f"{provider.client_id}:{provider.client_secret}".encode()
@ -87,7 +93,9 @@ class TestToken(OAuthTestCase):
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
params = TokenParams.from_request(request)
params = TokenParams.parse(
request, provider, provider.client_id, provider.client_secret
)
self.assertEqual(params.provider, provider)
def test_auth_code_view(self):
@ -98,6 +106,7 @@ class TestToken(OAuthTestCase):
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
rsa_key=CertificateKeyPair.objects.first(),
)
# Needs to be assigned to an application for iss to be set
self.app.provider = provider
@ -141,6 +150,7 @@ class TestToken(OAuthTestCase):
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
rsa_key=CertificateKeyPair.objects.first(),
)
# Needs to be assigned to an application for iss to be set
self.app.provider = provider
@ -193,6 +203,7 @@ class TestToken(OAuthTestCase):
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
rsa_key=CertificateKeyPair.objects.first(),
)
header = b64encode(
f"{provider.client_id}:{provider.client_secret}".encode()
@ -230,3 +241,65 @@ class TestToken(OAuthTestCase):
),
},
)
def test_refresh_token_revoke(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://testserver",
rsa_key=CertificateKeyPair.objects.first(),
)
# Needs to be assigned to an application for iss to be set
self.app.provider = provider
self.app.save()
header = b64encode(
f"{provider.client_id}:{provider.client_secret}".encode()
).decode()
user = User.objects.get(username="akadmin")
token: RefreshToken = RefreshToken.objects.create(
provider=provider,
user=user,
refresh_token=generate_client_id(),
)
# Create initial refresh token
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
data={
"grant_type": GRANT_TYPE_REFRESH_TOKEN,
"refresh_token": token.refresh_token,
"redirect_uri": "http://testserver",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
new_token: RefreshToken = (
RefreshToken.objects.filter(user=user).exclude(pk=token.pk).first()
)
# Post again with initial token -> get new refresh token
# and revoke old one
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
data={
"grant_type": GRANT_TYPE_REFRESH_TOKEN,
"refresh_token": new_token.refresh_token,
"redirect_uri": "http://local.invalid",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
self.assertEqual(response.status_code, 200)
# Post again with old token, is now revoked and should error
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
data={
"grant_type": GRANT_TYPE_REFRESH_TOKEN,
"refresh_token": new_token.refresh_token,
"redirect_uri": "http://local.invalid",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
self.assertEqual(response.status_code, 400)
self.assertTrue(
Event.objects.filter(action=EventAction.SUSPICIOUS_REQUEST).exists()
)

View File

@ -10,6 +10,7 @@ from django.http.response import HttpResponseRedirect
from django.utils.cache import patch_vary_headers
from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction
from authentik.providers.oauth2.errors import BearerTokenError
from authentik.providers.oauth2.models import RefreshToken
@ -50,7 +51,7 @@ def cors_allow(request: HttpRequest, response: HttpResponse, *allowed_origins: s
if not allowed:
LOGGER.warning(
"CORS: Origin is not an allowed origin",
requested=origin,
requested=received_origin,
allowed=allowed_origins,
)
return response
@ -132,22 +133,31 @@ def protected_resource_view(scopes: list[str]):
raise BearerTokenError("invalid_token")
try:
kwargs["token"] = RefreshToken.objects.get(
token: RefreshToken = RefreshToken.objects.get(
access_token=access_token
)
except RefreshToken.DoesNotExist:
LOGGER.debug("Token does not exist", access_token=access_token)
raise BearerTokenError("invalid_token")
if kwargs["token"].is_expired:
if token.is_expired:
LOGGER.debug("Token has expired", access_token=access_token)
raise BearerTokenError("invalid_token")
if not set(scopes).issubset(set(kwargs["token"].scope)):
if token.revoked:
LOGGER.warning("Revoked token was used", access_token=access_token)
Event.new(
action=EventAction.SUSPICIOUS_REQUEST,
message="Revoked refresh token was used",
token=access_token,
).from_http(request)
raise BearerTokenError("invalid_token")
if not set(scopes).issubset(set(token.scope)):
LOGGER.warning(
"Scope missmatch.",
required=set(scopes),
token_has=set(kwargs["token"].scope),
token_has=set(token.scope),
)
raise BearerTokenError("insufficient_scope")
except BearerTokenError as error:
@ -156,7 +166,7 @@ def protected_resource_view(scopes: list[str]):
"WWW-Authenticate"
] = f'error="{error.code}", error_description="{error.description}"'
return response
kwargs["token"] = token
return view(request, *args, **kwargs)
return view_wrapper

View File

@ -156,20 +156,23 @@ class OAuthAuthorizationParams:
def check_redirect_uri(self):
"""Redirect URI validation."""
allowed_redirect_urls = self.provider.redirect_uris.split()
if not self.redirect_uri:
LOGGER.warning("Missing redirect uri.")
raise RedirectUriError("", self.provider.redirect_uris.split())
if self.redirect_uri.lower() not in [
x.lower() for x in self.provider.redirect_uris.split()
]:
raise RedirectUriError("", allowed_redirect_urls)
if len(allowed_redirect_urls) < 1:
LOGGER.warning(
"Provider has no allowed redirect_uri set, allowing all.",
allow=self.redirect_uri.lower(),
)
return
if self.redirect_uri.lower() not in [x.lower() for x in allowed_redirect_urls]:
LOGGER.warning(
"Invalid redirect uri",
redirect_uri=self.redirect_uri,
excepted=self.provider.redirect_uris.split(),
)
raise RedirectUriError(
self.redirect_uri, self.provider.redirect_uris.split()
excepted=allowed_redirect_urls,
)
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
if self.request:
raise AuthorizeError(
self.redirect_uri, "request_not_supported", self.grant_type, self.state
@ -189,6 +192,10 @@ class OAuthAuthorizationParams:
def check_nonce(self):
"""Nonce parameter validation."""
# https://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
# Nonce is only required for Implicit flows
if self.grant_type != GrantTypes.IMPLICIT:
return
if not self.nonce:
self.nonce = self.state
LOGGER.warning("Using state as nonce for OpenID Request")

View File

@ -8,6 +8,7 @@ from django.http import HttpRequest, HttpResponse
from django.views import View
from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.constants import (
GRANT_TYPE_AUTHORIZATION_CODE,
@ -30,6 +31,7 @@ LOGGER = get_logger()
@dataclass
# pylint: disable=too-many-instance-attributes
class TokenParams:
"""Token params"""
@ -40,6 +42,8 @@ class TokenParams:
state: str
scope: list[str]
provider: OAuth2Provider
authorization_code: Optional[AuthorizationCode] = None
refresh_token: Optional[RefreshToken] = None
@ -47,35 +51,34 @@ class TokenParams:
raw_code: InitVar[str] = ""
raw_token: InitVar[str] = ""
request: InitVar[Optional[HttpRequest]] = None
@staticmethod
def from_request(request: HttpRequest) -> "TokenParams":
"""Extract Token Parameters from http request"""
client_id, client_secret = extract_client_auth(request)
def parse(
request: HttpRequest,
provider: OAuth2Provider,
client_id: str,
client_secret: str,
) -> "TokenParams":
"""Parse params for request"""
return TokenParams(
# Init vars
raw_code=request.POST.get("code", ""),
raw_token=request.POST.get("refresh_token", ""),
request=request,
# Regular params
provider=provider,
client_id=client_id,
client_secret=client_secret,
redirect_uri=request.POST.get("redirect_uri", ""),
grant_type=request.POST.get("grant_type", ""),
raw_code=request.POST.get("code", ""),
raw_token=request.POST.get("refresh_token", ""),
state=request.POST.get("state", ""),
scope=request.POST.get("scope", "").split(),
# PKCE parameter.
code_verifier=request.POST.get("code_verifier"),
)
def __post_init__(self, raw_code, raw_token):
try:
provider: OAuth2Provider = OAuth2Provider.objects.get(
client_id=self.client_id
)
self.provider = provider
except OAuth2Provider.DoesNotExist:
LOGGER.warning("OAuth2Provider does not exist", client_id=self.client_id)
raise TokenError("invalid_client")
def __post_init__(self, raw_code: str, raw_token: str, request: HttpRequest):
if self.provider.client_type == ClientTypes.CONFIDENTIAL:
if self.provider.client_secret != self.client_secret:
LOGGER.warning(
@ -87,7 +90,6 @@ class TokenParams:
if self.grant_type == GRANT_TYPE_AUTHORIZATION_CODE:
self.__post_init_code(raw_code)
elif self.grant_type == GRANT_TYPE_REFRESH_TOKEN:
if not raw_token:
LOGGER.warning("Missing refresh token")
@ -107,7 +109,14 @@ class TokenParams:
token=raw_token,
)
raise TokenError("invalid_grant")
if self.refresh_token.revoked:
LOGGER.warning("Refresh token is revoked", token=raw_token)
Event.new(
action=EventAction.SUSPICIOUS_REQUEST,
message="Revoked refresh token was used",
token=raw_token,
).from_http(request)
raise TokenError("invalid_grant")
else:
LOGGER.warning("Invalid grant type", grant_type=self.grant_type)
raise TokenError("unsupported_grant_type")
@ -159,13 +168,14 @@ class TokenParams:
class TokenView(View):
"""Generate tokens for clients"""
provider: Optional[OAuth2Provider] = None
params: Optional[TokenParams] = None
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
response = super().dispatch(request, *args, **kwargs)
allowed_origins = []
if self.params:
allowed_origins = self.params.provider.redirect_uris.split("\n")
if self.provider:
allowed_origins = self.provider.redirect_uris.split("\n")
cors_allow(self.request, response, *allowed_origins)
return response
@ -175,19 +185,32 @@ class TokenView(View):
def post(self, request: HttpRequest) -> HttpResponse:
"""Generate tokens for clients"""
try:
self.params = TokenParams.from_request(request)
client_id, client_secret = extract_client_auth(request)
try:
self.provider = OAuth2Provider.objects.get(client_id=client_id)
except OAuth2Provider.DoesNotExist:
LOGGER.warning(
"OAuth2Provider does not exist", client_id=self.client_id
)
raise TokenError("invalid_client")
if not self.provider:
raise ValueError
self.params = TokenParams.parse(
request, self.provider, client_id, client_secret
)
if self.params.grant_type == GRANT_TYPE_AUTHORIZATION_CODE:
return TokenResponse(self.create_code_response_dic())
return TokenResponse(self.create_code_response())
if self.params.grant_type == GRANT_TYPE_REFRESH_TOKEN:
return TokenResponse(self.create_refresh_response_dic())
return TokenResponse(self.create_refresh_response())
raise ValueError(f"Invalid grant_type: {self.params.grant_type}")
except TokenError as error:
return TokenResponse(error.create_dict(), status=400)
except UserAuthError as error:
return TokenResponse(error.create_dict(), status=403)
def create_code_response_dic(self) -> dict[str, Any]:
def create_code_response(self) -> dict[str, Any]:
"""See https://tools.ietf.org/html/rfc6749#section-4.1"""
refresh_token = self.params.authorization_code.provider.create_refresh_token(
@ -211,7 +234,7 @@ class TokenView(View):
# We don't need to store the code anymore.
self.params.authorization_code.delete()
response_dict = {
return {
"access_token": refresh_token.access_token,
"refresh_token": refresh_token.refresh_token,
"token_type": "bearer",
@ -223,9 +246,7 @@ class TokenView(View):
"id_token": refresh_token.provider.encode(refresh_token.id_token.to_dict()),
}
return response_dict
def create_refresh_response_dic(self) -> dict[str, Any]:
def create_refresh_response(self) -> dict[str, Any]:
"""See https://tools.ietf.org/html/rfc6749#section-6"""
unauthorized_scopes = set(self.params.scope) - set(
@ -253,10 +274,11 @@ class TokenView(View):
# Store the refresh_token.
refresh_token.save()
# Forget the old token.
self.params.refresh_token.delete()
# Mark old token as revoked
self.params.refresh_token.revoked = True
self.params.refresh_token.save()
dic = {
return {
"access_token": refresh_token.access_token,
"refresh_token": refresh_token.refresh_token,
"token_type": "bearer",
@ -267,5 +289,3 @@ class TokenView(View):
),
"id_token": self.params.provider.encode(refresh_token.id_token.to_dict()),
}
return dic

View File

@ -1,6 +1,7 @@
"""authentik OAuth2 OpenID Userinfo views"""
from typing import Any, Optional
from deepmerge import always_merger
from django.http import HttpRequest, HttpResponse
from django.http.response import HttpResponseBadRequest
from django.views import View
@ -78,7 +79,7 @@ class UserInfoView(View):
)
continue
LOGGER.debug("updated scope", scope=scope)
final_claims.update(value)
always_merger.merge(final_claims, value)
return final_claims
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:

View File

@ -1,7 +1,7 @@
"""SAML AuthNRequest Parser and dataclass"""
from base64 import b64decode
from dataclasses import dataclass
from typing import Optional
from typing import Optional, Union
from urllib.parse import quote_plus
import xmlsec
@ -54,7 +54,9 @@ class AuthNRequestParser:
def __init__(self, provider: SAMLProvider):
self.provider = provider
def _parse_xml(self, decoded_xml: str, relay_state: Optional[str]) -> AuthNRequest:
def _parse_xml(
self, decoded_xml: Union[str, bytes], relay_state: Optional[str]
) -> AuthNRequest:
root = ElementTree.fromstring(decoded_xml)
request_acs_url = root.attrib["AssertionConsumerServiceURL"]
@ -79,10 +81,12 @@ class AuthNRequestParser:
return auth_n_request
def parse(self, saml_request: str, relay_state: Optional[str]) -> AuthNRequest:
def parse(
self, saml_request: str, relay_state: Optional[str] = None
) -> AuthNRequest:
"""Validate and parse raw request with enveloped signautre."""
try:
decoded_xml = b64decode(saml_request.encode()).decode()
decoded_xml = b64decode(saml_request.encode())
except UnicodeDecodeError:
raise CannotHandleAssertion(ERROR_CANNOT_DECODE_REQUEST)
@ -93,8 +97,9 @@ class AuthNRequestParser:
signature_nodes = root.xpath(
"/samlp:AuthnRequest/ds:Signature", namespaces=NS_MAP
)
if len(signature_nodes) != 1:
raise CannotHandleAssertion(ERROR_SIGNATURE_REQUIRED_BUT_ABSENT)
# No signatures, no verifier configured -> decode xml directly
if len(signature_nodes) < 1 and not verifier:
return self._parse_xml(decoded_xml, relay_state)
signature_node = signature_nodes[0]

View File

@ -14,13 +14,29 @@ from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.request_parser import AuthNRequestParser
from authentik.sources.saml.exceptions import MismatchedRequestID
from authentik.sources.saml.models import SAMLSource
from authentik.sources.saml.processors.constants import SAML_NAME_ID_FORMAT_UNSPECIFIED
from authentik.sources.saml.processors.constants import (
SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_UNSPECIFIED,
)
from authentik.sources.saml.processors.request import (
SESSION_REQUEST_ID,
RequestProcessor,
)
from authentik.sources.saml.processors.response import ResponseProcessor
POST_REQUEST = (
"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1sMn"
"A9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgQXNzZXJ0aW9uQ29uc3VtZXJTZXJ2aWNlVVJMPSJo"
"dHRwczovL2V1LWNlbnRyYWwtMS5zaWduaW4uYXdzLmFtYXpvbi5jb20vcGxhdGZvcm0vc2FtbC9hY3MvMmQ3MzdmOTYtNT"
"VmYi00MDM1LTk1M2UtNWUyNDEzNGViNzc4IiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9pZC5iZXJ5anUub3JnL2FwcGxpY2F0"
"aW9uL3NhbWwvYXdzLXNzby9zc28vYmluZGluZy9wb3N0LyIgSUQ9ImF3c19MRHhMR2V1YnBjNWx4MTJneENnUzZ1UGJpeD"
"F5ZDVyZSIgSXNzdWVJbnN0YW50PSIyMDIxLTA3LTA2VDE0OjIzOjA2LjM4OFoiIFByb3RvY29sQmluZGluZz0idXJuOm9h"
"c2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmJpbmRpbmdzOkhUVFAtUE9TVCIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIH"
"htbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwczovL2V1LWNlbnRyYWwt"
"MS5zaWduaW4uYXdzLmFtYXpvbi5jb20vcGxhdGZvcm0vc2FtbC9kLTk5NjcyZjgyNzg8L3NhbWwyOklzc3Vlcj48c2FtbD"
"JwOk5hbWVJRFBvbGljeSBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWls"
"QWRkcmVzcyIvPjwvc2FtbDJwOkF1dGhuUmVxdWVzdD4="
)
REDIRECT_REQUEST = (
"fZLNbsIwEIRfJfIdbKeFgEUipXAoEm0jSHvopTLJplhK7NTr9Oft6yRUKhekPdk73+yOdoWyqVuRdu6k9/DRAbrgu6k1iu"
"EjJp3VwkhUKLRsAIUrxCF92IlwykRrjTOFqUmQIoJ1yui10dg1YA9gP1UBz/tdTE7OtSgo5WzKQzYditGeP8GW9rSQZk+H"
@ -208,3 +224,22 @@ class TestAuthNRequest(TestCase):
self.assertEqual(parsed_request.id, "_dcf55fcd27a887e60a7ef9ee6fd3adab")
self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_UNSPECIFIED)
self.assertEqual(parsed_request.relay_state, REDIRECT_RELAY_STATE)
def test_signed_static(self):
"""Test post request with static request"""
provider = SAMLProvider(
name="aws",
authorization_flow=Flow.objects.get(
slug="default-provider-authorization-implicit-consent"
),
acs_url=(
"https://eu-central-1.signin.aws.amazon.com/platform/"
"saml/acs/2d737f96-55fb-4035-953e-5e24134eb778"
),
audience="https://10.120.20.200/saml-sp/SAML2/POST",
issuer="https://10.120.20.200/saml-sp/SAML2/POST",
signing_kp=CertificateKeyPair.objects.first(),
)
parsed_request = AuthNRequestParser(provider).parse(POST_REQUEST)
self.assertEqual(parsed_request.id, "aws_LDxLGeubpc5lx12gxCgS6uPbix1yd5re")
self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_EMAIL)

View File

@ -15,7 +15,7 @@ class MessageConsumer(JsonWebsocketConsumer):
cache.set(f"user_{self.session_key}_messages_{self.channel_name}", True, None)
# pylint: disable=unused-argument
def disconnect(self, close_code):
def disconnect(self, code):
cache.delete(f"user_{self.session_key}_messages_{self.channel_name}")
def event_update(self, event: dict):

View File

@ -0,0 +1,94 @@
"""Dynamically set SameSite depending if the upstream connection is TLS or not"""
import time
from django.conf import settings
from django.contrib.sessions.backends.base import UpdateError
from django.contrib.sessions.exceptions import SessionInterrupted
from django.contrib.sessions.middleware import (
SessionMiddleware as UpstreamSessionMiddleware,
)
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.utils.cache import patch_vary_headers
from django.utils.http import http_date
class SessionMiddleware(UpstreamSessionMiddleware):
"""Dynamically set SameSite depending if the upstream connection is TLS or not"""
@staticmethod
def is_secure(request: HttpRequest) -> bool:
"""Check if request is TLS'd or localhost"""
if request.is_secure():
return True
host, _, _ = request.get_host().partition(":")
if host == "localhost" and settings.DEBUG:
# Since go does not consider localhost with http a secure origin
# we can't set the secure flag.
user_agent = request.META.get("HTTP_USER_AGENT", "")
if user_agent.startswith("authentik-outpost@"):
return False
return True
return False
def process_response(
self, request: HttpRequest, response: HttpResponse
) -> HttpResponse:
"""
If request.session was modified, or if the configuration is to save the
session every time, save the changes and set a session cookie or delete
the session cookie if the session has been emptied.
"""
try:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
return response
# Set SameSite based on whether or not the request is secure
secure = SessionMiddleware.is_secure(request)
same_site = "None" if secure else "Lax"
# First check if we need to delete this cookie.
# The session should be deleted only if the session is entirely empty.
if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
response.delete_cookie(
settings.SESSION_COOKIE_NAME,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
samesite=same_site,
)
patch_vary_headers(response, ("Cookie",))
else:
if accessed:
patch_vary_headers(response, ("Cookie",))
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = http_date(expires_time)
# Save the session data and refresh the client cookie.
# Skip session save for 500 responses, refs #3881.
if response.status_code != 500:
try:
request.session.save()
except UpdateError:
raise SessionInterrupted(
"The request's session was deleted before the "
"request completed. The user may have logged "
"out in a concurrent request, for example."
)
response.set_cookie(
settings.SESSION_COOKIE_NAME,
request.session.session_key,
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=secure,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
samesite=same_site,
)
return response

View File

@ -188,12 +188,19 @@ REST_FRAMEWORK = {
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
REDIS_PROTOCOL_PREFIX = "redis://"
REDIS_CELERY_TLS_REQUIREMENTS = ""
if CONFIG.y_bool("redis.tls", False):
REDIS_PROTOCOL_PREFIX = "rediss://"
REDIS_CELERY_TLS_REQUIREMENTS = f"?ssl_cert_reqs={CONFIG.y('redis.tls_reqs')}"
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": (
f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:6379"
f"/{CONFIG.y('redis.cache_db')}"
f"{REDIS_PROTOCOL_PREFIX}:"
f"{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:"
f"{int(CONFIG.y('redis.port'))}/{CONFIG.y('redis.cache_db')}"
),
"TIMEOUT": int(CONFIG.y("redis.cache_timeout", 300)),
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
@ -203,14 +210,16 @@ DJANGO_REDIS_IGNORE_EXCEPTIONS = True
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
SESSION_COOKIE_SAMESITE = "lax"
# Configured via custom SessionMiddleware
# SESSION_COOKIE_SAMESITE = "None"
# SESSION_COOKIE_SECURE = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
MESSAGE_STORAGE = "authentik.root.messages.storage.ChannelsStorage"
MIDDLEWARE = [
"django_prometheus.middleware.PrometheusBeforeMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"authentik.root.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"authentik.core.middleware.RequestIDMiddleware",
"authentik.tenants.middleware.TenantMiddleware",
@ -250,8 +259,9 @@ CHANNEL_LAYERS = {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [
f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:6379"
f"/{CONFIG.y('redis.ws_db')}"
f"{REDIS_PROTOCOL_PREFIX}:"
f"{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:"
f"{int(CONFIG.y('redis.port'))}/{CONFIG.y('redis.ws_db')}"
],
},
},
@ -329,12 +339,16 @@ CELERY_BEAT_SCHEDULE = {
CELERY_TASK_CREATE_MISSING_QUEUES = True
CELERY_TASK_DEFAULT_QUEUE = "authentik"
CELERY_BROKER_URL = (
f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}"
f":6379/{CONFIG.y('redis.message_queue_db')}"
f"{REDIS_PROTOCOL_PREFIX}:"
f"{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:"
f"{int(CONFIG.y('redis.port'))}/{CONFIG.y('redis.message_queue_db')}"
f"{REDIS_CELERY_TLS_REQUIREMENTS}"
)
CELERY_RESULT_BACKEND = (
f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}"
f":6379/{CONFIG.y('redis.message_queue_db')}"
f"{REDIS_PROTOCOL_PREFIX}:"
f"{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:"
f"{int(CONFIG.y('redis.port'))}/{CONFIG.y('redis.message_queue_db')}"
f"{REDIS_CELERY_TLS_REQUIREMENTS}"
)
# Database backup
@ -362,11 +376,12 @@ if CONFIG.y("postgresql.s3_backup"):
)
# Sentry integration
SENTRY_DSN = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
_ERROR_REPORTING = CONFIG.y_bool("error_reporting.enabled", False)
if _ERROR_REPORTING:
# pylint: disable=abstract-class-instantiated
sentry_init(
dsn="https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
dsn=SENTRY_DSN,
integrations=[
DjangoIntegration(transaction_style="function_name"),
CeleryIntegration(),
@ -401,9 +416,8 @@ MEDIA_URL = "/media/"
TEST = False
TEST_RUNNER = "authentik.root.test_runner.PytestTestRunner"
LOG_LEVEL = CONFIG.y("log_level").upper() if not TEST else "DEBUG"
# We can't check TEST here as its set later by the test runner
LOG_LEVEL = CONFIG.y("log_level").upper() if "TF_BUILD" not in os.environ else "DEBUG"
structlog.configure_once(
processors=[

View File

@ -36,7 +36,8 @@ class SourceType:
class SourceTypeManager:
"""Manager to hold all Source types."""
__sources: list[SourceType] = []
def __init__(self) -> None:
self.__sources: list[SourceType] = []
def type(self):
"""Class decorator to register classes inline."""

View File

@ -1,4 +1,5 @@
"""OAuth Callback Views"""
from json import JSONDecodeError
from typing import Any, Optional
from django.conf import settings
@ -10,6 +11,7 @@ from django.views.generic import View
from structlog.stdlib import get_logger
from authentik.core.sources.flow_manager import SourceFlowManager
from authentik.events.models import Event, EventAction
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from authentik.sources.oauth.views.base import OAuthClientMixin
@ -42,8 +44,16 @@ class OAuthCallback(OAuthClientMixin, View):
if "error" in token:
return self.handle_login_failure(token["error"])
# Fetch profile info
raw_info = client.get_profile_info(token)
if raw_info is None:
try:
raw_info = client.get_profile_info(token)
if raw_info is None:
return self.handle_login_failure("Could not retrieve profile.")
except JSONDecodeError as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message="Failed to JSON-decode profile.",
raw_profile=exc.doc,
).from_http(self.request)
return self.handle_login_failure("Could not retrieve profile.")
identifier = self.get_user_id(raw_info)
if identifier is None:

View File

@ -74,12 +74,12 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
duo, self.stage.request, self.stage.get_pending_user()
)
def validate(self, data: dict):
def validate(self, attrs: dict):
# Checking if the given data is from a valid device class is done above
# Here we only check if the any data was sent at all
if "code" not in data and "webauthn" not in data and "duo" not in data:
if "code" not in attrs and "webauthn" not in attrs and "duo" not in attrs:
raise ValidationError("Empty response")
return data
return attrs
class AuthenticatorValidateStageView(ChallengeStageView):
@ -163,7 +163,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
# pylint: disable=unused-argument
def challenge_valid(
self, challenge: AuthenticatorValidationChallengeResponse
self, response: AuthenticatorValidationChallengeResponse
) -> HttpResponse:
# All validation is done by the serializer
return self.executor.stage_ok()

View File

@ -38,7 +38,7 @@ class EmailChallengeResponse(ChallengeResponse):
component = CharField(default="ak-stage-email")
def validate(self, data):
def validate(self, attrs):
raise ValidationError("")

View File

@ -73,9 +73,9 @@ class IdentificationChallengeResponse(ChallengeResponse):
pre_user: Optional[User] = None
def validate(self, data: dict[str, Any]) -> dict[str, Any]:
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
"""Validate that user exists, and optionally their password"""
uid_field = data["uid_field"]
uid_field = attrs["uid_field"]
current_stage: IdentificationStage = self.stage.executor.current_stage
pre_user = self.stage.get_user(uid_field)
@ -101,9 +101,9 @@ class IdentificationChallengeResponse(ChallengeResponse):
self.pre_user = pre_user
if not current_stage.password_stage:
# No password stage select, don't validate the password
return data
return attrs
password = data["password"]
password = attrs["password"]
try:
user = authenticate(
self.stage.request,
@ -116,7 +116,7 @@ class IdentificationChallengeResponse(ChallengeResponse):
self.pre_user = user
except PermissionDenied as exc:
raise ValidationError(str(exc)) from exc
return data
return attrs
class IdentificationStageView(ChallengeStageView):

View File

@ -146,8 +146,6 @@ def password_single_validator_factory() -> Callable[[PromptChallenge, str], Any]
class ListPolicyEngine(PolicyEngine):
"""Slightly modified policy engine, which uses a list instead of a PolicyBindingModel"""
__list: list[Policy]
def __init__(
self, policies: list[Policy], user: User, request: HttpRequest = None
) -> None:

View File

@ -24,6 +24,10 @@ LOGGER = get_logger()
class UserWriteStageView(StageView):
"""Finalise Enrollment flow by creating a user object."""
def post(self, request: HttpRequest) -> HttpResponse:
"""Wrapper for post requests"""
return self.get(request)
def get(self, request: HttpRequest) -> HttpResponse:
"""Save data in the current flow to the currently pending user. If no user is pending,
a new user is created."""

View File

@ -27,7 +27,7 @@ stages:
script: make gen-outpost
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'outpost/api/'
targetPath: 'api/'
artifact: 'go_api_client'
publishLocation: 'pipeline'
- stage: lint
@ -43,17 +43,19 @@ stages:
inputs:
buildType: 'current'
artifactName: 'go_api_client'
path: "outpost/api/"
path: "api/"
- task: CmdLine@2
inputs:
script: |
mkdir -p web/dist
mkdir -p website/help
touch web/dist/test website/help/test
docker run \
--rm \
-v $(pwd):/app \
-w /app \
golangci/golangci-lint:v1.39.0 \
golangci-lint run -v --timeout 200s
workingDirectory: 'outpost/'
- stage: build_docker
jobs:
- job: proxy_build_docker
@ -73,7 +75,7 @@ stages:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/outpost-proxy'
command: 'build'
Dockerfile: 'outpost/proxy.Dockerfile'
Dockerfile: 'proxy.Dockerfile'
buildContext: '$(Build.SourcesDirectory)'
tags: |
gh-$(branchName)
@ -106,7 +108,7 @@ stages:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/outpost-ldap'
command: 'build'
Dockerfile: 'outpost/ldap.Dockerfile'
Dockerfile: 'ldap.Dockerfile'
buildContext: '$(Build.SourcesDirectory)'
tags: |
gh-$(branchName)

View File

@ -2,16 +2,14 @@ package main
import (
"fmt"
"math/rand"
"net/url"
"os"
"os/signal"
"time"
log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg/ak"
"goauthentik.io/outpost/pkg/ldap"
"goauthentik.io/internal/common"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/ldap"
)
const helpMessage = `authentik ldap
@ -23,32 +21,30 @@ Required environment variables:
func main() {
log.SetLevel(log.DebugLevel)
pbURL, found := os.LookupEnv("AUTHENTIK_HOST")
akURL, found := os.LookupEnv("AUTHENTIK_HOST")
if !found {
fmt.Println("env AUTHENTIK_HOST not set!")
fmt.Println(helpMessage)
os.Exit(1)
}
pbToken, found := os.LookupEnv("AUTHENTIK_TOKEN")
akToken, found := os.LookupEnv("AUTHENTIK_TOKEN")
if !found {
fmt.Println("env AUTHENTIK_TOKEN not set!")
fmt.Println(helpMessage)
os.Exit(1)
}
pbURLActual, err := url.Parse(pbURL)
akURLActual, err := url.Parse(akURL)
if err != nil {
fmt.Println(err)
fmt.Println(helpMessage)
os.Exit(1)
}
rand.Seed(time.Now().UnixNano())
ex := common.Init()
defer common.Defer()
ac := ak.NewAPIController(*pbURLActual, pbToken)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
ac := ak.NewAPIController(*akURLActual, akToken)
ac.Server = ldap.NewServer(ac)
@ -58,7 +54,7 @@ func main() {
}
for {
<-interrupt
<-ex
ac.Shutdown()
os.Exit(0)
}

View File

@ -2,16 +2,14 @@ package main
import (
"fmt"
"math/rand"
"net/url"
"os"
"os/signal"
"time"
log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg/ak"
"goauthentik.io/outpost/pkg/proxy"
"goauthentik.io/internal/common"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxy"
)
const helpMessage = `authentik proxy
@ -23,32 +21,30 @@ Required environment variables:
func main() {
log.SetLevel(log.DebugLevel)
pbURL, found := os.LookupEnv("AUTHENTIK_HOST")
akURL, found := os.LookupEnv("AUTHENTIK_HOST")
if !found {
fmt.Println("env AUTHENTIK_HOST not set!")
fmt.Println(helpMessage)
os.Exit(1)
}
pbToken, found := os.LookupEnv("AUTHENTIK_TOKEN")
akToken, found := os.LookupEnv("AUTHENTIK_TOKEN")
if !found {
fmt.Println("env AUTHENTIK_TOKEN not set!")
fmt.Println(helpMessage)
os.Exit(1)
}
pbURLActual, err := url.Parse(pbURL)
akURLActual, err := url.Parse(akURL)
if err != nil {
fmt.Println(err)
fmt.Println(helpMessage)
os.Exit(1)
}
rand.Seed(time.Now().UnixNano())
ex := common.Init()
defer common.Defer()
ac := ak.NewAPIController(*pbURLActual, pbToken)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
ac := ak.NewAPIController(*akURLActual, akToken)
ac.Server = proxy.NewServer(ac)
@ -58,7 +54,7 @@ func main() {
}
for {
<-interrupt
<-ex
ac.Shutdown()
os.Exit(0)
}

View File

@ -2,51 +2,106 @@ package main
import (
"fmt"
"sync"
"time"
"github.com/getsentry/sentry-go"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/common"
"goauthentik.io/internal/config"
"goauthentik.io/internal/constants"
"goauthentik.io/internal/gounicorn"
"goauthentik.io/internal/web"
)
var running = true
func main() {
log.SetLevel(log.DebugLevel)
config.DefaultConfig()
config.LoadConfig("./authentik/lib/default.yml")
config.LoadConfig("./local.env.yml")
err := config.LoadConfig("./authentik/lib/default.yml")
if err != nil {
log.WithError(err).Warning("failed to load default config")
}
err = config.LoadConfig("./local.env.yml")
if err != nil {
log.WithError(err).Debug("failed to local config")
}
config.ConfigureLogger()
if config.G.ErrorReporting.Enabled {
sentry.Init(sentry.ClientOptions{
err := sentry.Init(sentry.ClientOptions{
Dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
AttachStacktrace: true,
TracesSampleRate: 0.6,
Release: fmt.Sprintf("authentik@%s", constants.VERSION),
Environment: config.G.ErrorReporting.Environment,
})
defer sentry.Flush(time.Second * 5)
defer sentry.Recover()
if err != nil {
log.WithError(err).Warning("failed to init sentry")
}
}
rl := log.WithField("logger", "authentik.g")
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
g := gounicorn.NewGoUnicorn()
for {
err := g.Start()
rl.WithError(err).Warning("gunicorn process died, restarting")
}
}()
go func() {
defer wg.Done()
ws := web.NewWebServer()
ws.Run()
}()
wg.Wait()
ex := common.Init()
defer common.Defer()
// u, _ := url.Parse("http://localhost:8000")
g := gounicorn.NewGoUnicorn()
ws := web.NewWebServer()
defer g.Kill()
defer ws.Shutdown()
for {
go attemptStartBackend(g)
ws.Start()
// go attemptProxyStart(u)
<-ex
running = false
log.WithField("logger", "authentik").Info("shutting down webserver")
go ws.Shutdown()
log.WithField("logger", "authentik").Info("killing gunicorn")
g.Kill()
}
}
func attemptStartBackend(g *gounicorn.GoUnicorn) {
for {
err := g.Start()
if !running {
return
}
log.WithField("logger", "authentik.g").WithError(err).Warning("gunicorn process died, restarting")
}
}
// func attemptProxyStart(u *url.URL) error {
// maxTries := 100
// attempt := 0
// for {
// log.WithField("logger", "authentik").Debug("attempting to init outpost")
// ac := ak.NewAPIController(*u, config.G.SecretKey)
// if ac == nil {
// attempt += 1
// time.Sleep(1 * time.Second)
// if attempt > maxTries {
// break
// }
// continue
// }
// ac.Server = proxy.NewServer(ac)
// err := ac.Start()
// log.WithField("logger", "authentik").Debug("attempting to start outpost")
// if err != nil {
// attempt += 1
// time.Sleep(5 * time.Second)
// if attempt > maxTries {
// break
// }
// continue
// }
// if !running {
// ac.Shutdown()
// return nil
// }
// }
// return nil
// }

View File

@ -21,7 +21,7 @@ services:
networks:
- internal
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.3}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.1-rc1}
restart: unless-stopped
command: server
environment:
@ -44,7 +44,7 @@ services:
- "0.0.0.0:9000:9000"
- "0.0.0.0:9443:9443"
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.3}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.1-rc1}
restart: unless-stopped
command: worker
networks:

50
go.mod
View File

@ -3,11 +3,49 @@ module goauthentik.io
go 1.16
require (
github.com/getsentry/sentry-go v0.10.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/getsentry/sentry-go v0.11.0
github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-openapi/analysis v0.20.1 // indirect
github.com/go-openapi/errors v0.20.0 // indirect
github.com/go-openapi/runtime v0.19.29
github.com/go-openapi/strfmt v0.20.1
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-openapi/validate v0.20.2 // indirect
github.com/go-redis/redis/v7 v7.4.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
github.com/imdario/mergo v0.3.12
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/justinas/alice v1.2.0
github.com/kr/pretty v0.2.1 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 // indirect
github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3
github.com/oauth2-proxy/oauth2-proxy v0.0.0-20200831161845-e4e5580852dc
github.com/pelletier/go-toml v1.9.1 // indirect
github.com/pires/go-proxyproto v0.6.0 // indirect
github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
github.com/recws-org/recws v1.3.1
github.com/sirupsen/logrus v1.8.1
gopkg.in/yaml.v2 v2.3.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1 // indirect
go.mongodb.org/mongo-driver v1.5.2 // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 // indirect
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v2 v2.4.0
)

859
go.sum

File diff suppressed because it is too large Load Diff

22
internal/common/global.go Normal file
View File

@ -0,0 +1,22 @@
package common
import (
"math/rand"
"os"
"os/signal"
"time"
"github.com/getsentry/sentry-go"
)
func Init() chan os.Signal {
rand.Seed(time.Now().UnixNano())
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
return interrupt
}
func Defer() {
defer sentry.Flush(time.Second * 5)
defer sentry.Recover()
}

View File

@ -2,6 +2,7 @@ package config
type Config struct {
Debug bool `yaml:"debug"`
SecretKey string `yaml:"secret_key"`
Web WebConfig `yaml:"web"`
Paths PathsConfig `yaml:"paths"`
LogLevel string `yaml:"log_level"`

View File

@ -1,3 +1,20 @@
package constants
const VERSION = "2021.6.3"
import (
"fmt"
"os"
)
func BUILD() string {
build := os.Getenv("GIT_BUILD_HASH")
if build == "" {
return "tagged"
}
return build
}
func OutpostUserAgent() string {
return fmt.Sprintf("authentik-outpost@%s (%s)", VERSION, BUILD())
}
const VERSION = "2021.7.1-rc1"

View File

@ -9,16 +9,24 @@ import (
)
type GoUnicorn struct {
log *log.Entry
log *log.Entry
p *exec.Cmd
started bool
killed bool
}
func NewGoUnicorn() *GoUnicorn {
return &GoUnicorn{
log: log.WithField("logger", "authentik.g.unicorn"),
logger := log.WithField("logger", "authentik.g.unicorn")
g := &GoUnicorn{
log: logger,
started: false,
killed: false,
}
g.initCmd()
return g
}
func (g *GoUnicorn) Start() error {
func (g *GoUnicorn) initCmd() {
command := "gunicorn"
args := []string{"-c", "./lifecycle/gunicorn.conf.py", "authentik.root.asgi:application"}
if config.G.Debug {
@ -26,11 +34,28 @@ func (g *GoUnicorn) Start() error {
args = []string{"manage.py", "runserver", "localhost:8000"}
}
g.log.WithField("args", args).WithField("cmd", command).Debug("Starting gunicorn")
p := exec.Command(command, args...)
p.Env = append(os.Environ(),
"WORKERS=2",
)
p.Stdout = os.Stdout
p.Stderr = os.Stderr
return p.Run()
g.p = exec.Command(command, args...)
g.p.Env = os.Environ()
g.p.Stdout = os.Stdout
g.p.Stderr = os.Stderr
}
func (g *GoUnicorn) Start() error {
if g.killed {
g.log.Debug("Not restarting gunicorn since we're killed")
return nil
}
if g.started {
g.initCmd()
}
g.started = true
return g.p.Run()
}
func (g *GoUnicorn) Kill() {
g.killed = true
err := g.p.Process.Kill()
if err != nil {
g.log.WithError(err).Warning("failed to kill gunicorn")
}
}

View File

@ -6,15 +6,14 @@ import (
"math/rand"
"net/http"
"net/url"
"os"
"time"
"github.com/go-openapi/strfmt"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/recws-org/recws"
"goauthentik.io/outpost/api"
"goauthentik.io/outpost/pkg"
"goauthentik.io/api"
"goauthentik.io/internal/constants"
log "github.com/sirupsen/logrus"
)
@ -41,10 +40,11 @@ type APIController struct {
// NewAPIController initialise new API Controller instance from URL and API token
func NewAPIController(akURL url.URL, token string) *APIController {
config := api.NewConfiguration()
config.UserAgent = constants.OutpostUserAgent()
config.Host = akURL.Host
config.Scheme = akURL.Scheme
config.HTTPClient = &http.Client{
Transport: SetUserAgent(GetTLSTransport(), pkg.UserAgent()),
Transport: GetTLSTransport(),
}
config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))
@ -59,7 +59,7 @@ func NewAPIController(akURL url.URL, token string) *APIController {
if err != nil {
log.WithError(err).Error("Failed to fetch configuration")
os.Exit(1)
return nil
}
outpost := outposts.Results[0]
doGlobalSetup(outpost.Config)

View File

@ -3,7 +3,7 @@ package ak
import (
"context"
"goauthentik.io/outpost/api"
"goauthentik.io/api"
)
func (a *APIController) Update() ([]api.ProxyOutpostConfig, error) {

View File

@ -12,7 +12,7 @@ import (
"github.com/go-openapi/strfmt"
"github.com/gorilla/websocket"
"github.com/recws-org/recws"
"goauthentik.io/outpost/pkg"
"goauthentik.io/internal/constants"
)
func (ac *APIController) initWS(akURL url.URL, outpostUUID strfmt.UUID) {
@ -23,7 +23,7 @@ func (ac *APIController) initWS(akURL url.URL, outpostUUID strfmt.UUID) {
header := http.Header{
"Authorization": []string{authHeader},
"User-Agent": []string{pkg.UserAgent()},
"User-Agent": []string{constants.OutpostUserAgent()},
}
value, set := os.LookupEnv("AUTHENTIK_INSECURE")
@ -46,8 +46,8 @@ func (ac *APIController) initWS(akURL url.URL, outpostUUID strfmt.UUID) {
msg := websocketMessage{
Instruction: WebsocketInstructionHello,
Args: map[string]interface{}{
"version": pkg.VERSION,
"buildHash": pkg.BUILD(),
"version": constants.VERSION,
"buildHash": constants.BUILD(),
"uuid": ac.instanceUUID.String(),
},
}
@ -101,8 +101,8 @@ func (ac *APIController) startWSHealth() {
aliveMsg := websocketMessage{
Instruction: WebsocketInstructionHello,
Args: map[string]interface{}{
"version": pkg.VERSION,
"buildHash": pkg.BUILD(),
"version": constants.VERSION,
"buildHash": constants.BUILD(),
"uuid": ac.instanceUUID.String(),
},
}

View File

@ -1,6 +1,8 @@
package ak
import (
"context"
"crypto/tls"
"net/http"
"os"
"strings"
@ -9,7 +11,8 @@ import (
"github.com/getsentry/sentry-go"
httptransport "github.com/go-openapi/runtime/client"
log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg"
"goauthentik.io/api"
"goauthentik.io/internal/constants"
)
func doGlobalSetup(config map[string]interface{}) {
@ -33,7 +36,7 @@ func doGlobalSetup(config map[string]interface{}) {
default:
log.SetLevel(log.DebugLevel)
}
log.WithField("buildHash", pkg.BUILD()).WithField("version", pkg.VERSION).Info("Starting authentik outpost")
log.WithField("buildHash", constants.BUILD()).WithField("version", constants.VERSION).Info("Starting authentik outpost")
var dsn string
if config[ConfigErrorReportingEnabled].(bool) {
@ -66,3 +69,21 @@ func GetTLSTransport() http.RoundTripper {
}
return tlsTransport
}
// ParseCertificate Load certificate from Keyepair UUID and parse it into a go Certificate
func ParseCertificate(kpUuid string, cryptoApi *api.CryptoApiService) (*tls.Certificate, error) {
cert, _, err := cryptoApi.CryptoCertificatekeypairsViewCertificateRetrieve(context.Background(), kpUuid).Execute()
if err != nil {
return nil, err
}
key, _, err := cryptoApi.CryptoCertificatekeypairsViewPrivateKeyRetrieve(context.Background(), kpUuid).Execute()
if err != nil {
return nil, err
}
x509cert, err := tls.X509KeyPair([]byte(cert.Data), []byte(key.Data))
if err != nil {
return nil, err
}
return &x509cert, nil
}

177
internal/outpost/flow.go Normal file
View File

@ -0,0 +1,177 @@
package outpost
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"net/http/cookiejar"
"net/url"
"strconv"
"strings"
log "github.com/sirupsen/logrus"
"goauthentik.io/api"
"goauthentik.io/internal/constants"
"goauthentik.io/internal/outpost/ak"
)
type StageComponent string
const (
StageIdentification = StageComponent("ak-stage-identification")
StagePassword = StageComponent("ak-stage-password")
StageAuthenticatorValidate = StageComponent("ak-stage-authenticator-validate")
StageAccessDenied = StageComponent("ak-stage-access-denied")
)
const (
HeaderAuthentikRemoteIP = "X-authentik-remote-ip"
HeaderAuthentikOutpostToken = "X-authentik-outpost-token"
)
type FlowExecutor struct {
Params url.Values
Answers map[StageComponent]string
api *api.APIClient
flowSlug string
log *log.Entry
token string
}
func NewFlowExecutor(flowSlug string, refConfig *api.Configuration, logFields log.Fields) *FlowExecutor {
l := log.WithField("flow", flowSlug).WithFields(logFields)
jar, err := cookiejar.New(nil)
if err != nil {
l.WithError(err).Warning("Failed to create cookiejar")
panic(err)
}
// Create new http client that also sets the correct ip
config := api.NewConfiguration()
config.Host = refConfig.Host
config.Scheme = refConfig.Scheme
config.UserAgent = constants.OutpostUserAgent()
config.HTTPClient = &http.Client{
Jar: jar,
Transport: ak.GetTLSTransport(),
}
apiClient := api.NewAPIClient(config)
return &FlowExecutor{
Params: url.Values{},
Answers: make(map[StageComponent]string),
api: apiClient,
flowSlug: flowSlug,
log: l,
token: strings.Split(refConfig.DefaultHeader["Authorization"], " ")[1],
}
}
func (fe *FlowExecutor) ApiClient() *api.APIClient {
return fe.api
}
type ChallengeInt interface {
GetComponent() string
GetType() api.ChallengeChoices
GetResponseErrors() map[string][]api.ErrorDetail
}
func (fe *FlowExecutor) DelegateClientIP(a net.Addr) {
host, _, err := net.SplitHostPort(a.String())
if err != nil {
fe.log.WithError(err).Warning("Failed to get remote IP")
return
}
fe.api.GetConfig().AddDefaultHeader(HeaderAuthentikRemoteIP, host)
fe.api.GetConfig().AddDefaultHeader(HeaderAuthentikOutpostToken, fe.token)
}
func (fe *FlowExecutor) CheckApplicationAccess(appSlug string) (bool, error) {
p, _, err := fe.api.CoreApi.CoreApplicationsCheckAccessRetrieve(context.Background(), appSlug).Execute()
if !p.Passing {
fe.log.Info("Access denied for user")
return false, nil
}
if err != nil {
return false, fmt.Errorf("failed to check access: %w", err)
}
fe.log.Debug("User has access")
return true, nil
}
func (fe *FlowExecutor) getAnswer(stage StageComponent) string {
if v, o := fe.Answers[stage]; o {
return v
}
return ""
}
func (fe *FlowExecutor) Execute() (bool, error) {
return fe.solveFlowChallenge(1)
}
func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
req := fe.api.FlowsApi.FlowsExecutorGet(context.Background(), fe.flowSlug).Query(fe.Params.Encode())
challenge, _, err := req.Execute()
if err != nil {
return false, errors.New("failed to get challenge")
}
ch := challenge.GetActualInstance().(ChallengeInt)
fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got challenge")
responseReq := fe.api.FlowsApi.FlowsExecutorSolve(context.Background(), fe.flowSlug).Query(fe.Params.Encode())
switch ch.GetComponent() {
case string(StageIdentification):
responseReq = responseReq.FlowChallengeResponseRequest(api.IdentificationChallengeResponseRequestAsFlowChallengeResponseRequest(api.NewIdentificationChallengeResponseRequest(fe.getAnswer(StageIdentification))))
case string(StagePassword):
responseReq = responseReq.FlowChallengeResponseRequest(api.PasswordChallengeResponseRequestAsFlowChallengeResponseRequest(api.NewPasswordChallengeResponseRequest(fe.getAnswer(StagePassword))))
case string(StageAuthenticatorValidate):
// We only support duo as authenticator, check if that's allowed
var deviceChallenge *api.DeviceChallenge
for _, devCh := range challenge.AuthenticatorValidationChallenge.DeviceChallenges {
if devCh.DeviceClass == string(api.DEVICECLASSESENUM_DUO) {
deviceChallenge = &devCh
}
}
if deviceChallenge == nil {
return false, errors.New("got ak-stage-authenticator-validate without duo")
}
devId, err := strconv.Atoi(deviceChallenge.DeviceUid)
if err != nil {
return false, errors.New("failed to convert duo device id to int")
}
devId32 := int32(devId)
inner := api.NewAuthenticatorValidationChallengeResponseRequest()
inner.Duo = &devId32
responseReq = responseReq.FlowChallengeResponseRequest(api.AuthenticatorValidationChallengeResponseRequestAsFlowChallengeResponseRequest(inner))
case string(StageAccessDenied):
return false, errors.New("got ak-stage-access-denied")
default:
return false, fmt.Errorf("unsupported challenge type %s", ch.GetComponent())
}
response, _, err := responseReq.Execute()
ch = response.GetActualInstance().(ChallengeInt)
fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got response")
switch ch.GetComponent() {
case string(StageAccessDenied):
return false, errors.New("got ak-stage-access-denied")
}
if ch.GetType() == "redirect" {
return true, nil
}
if err != nil {
return false, fmt.Errorf("failed to submit challenge %w", err)
}
if len(ch.GetResponseErrors()) > 0 {
for key, errs := range ch.GetResponseErrors() {
for _, err := range errs {
return false, fmt.Errorf("flow error %s: %s", key, err.String)
}
}
}
if depth >= 10 {
return false, errors.New("exceeded stage recursion depth")
}
return fe.solveFlowChallenge(depth + 1)
}

View File

@ -2,14 +2,18 @@ package ldap
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"strings"
"sync"
"github.com/go-openapi/strfmt"
"github.com/pires/go-proxyproto"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ak"
)
func (ls *LDAPServer) Refresh() error {
@ -24,6 +28,7 @@ func (ls *LDAPServer) Refresh() error {
for idx, provider := range outposts.Results {
userDN := strings.ToLower(fmt.Sprintf("ou=users,%s", *provider.BaseDn))
groupDN := strings.ToLower(fmt.Sprintf("ou=groups,%s", *provider.BaseDn))
logger := log.WithField("logger", "authentik.outpost.ldap").WithField("provider", provider.Name)
providers[idx] = &ProviderInstance{
BaseDN: *provider.BaseDn,
GroupDN: groupDN,
@ -34,7 +39,20 @@ func (ls *LDAPServer) Refresh() error {
boundUsersMutex: sync.RWMutex{},
boundUsers: make(map[string]UserFlags),
s: ls,
log: log.WithField("logger", "authentik.outpost.ldap").WithField("provider", provider.Name),
log: logger,
tlsServerName: provider.TlsServerName,
uidStartNumber: *provider.UidStartNumber,
gidStartNumber: *provider.GidStartNumber,
}
if provider.Certificate.Get() != nil {
logger.WithField("provider", provider.Name).Debug("Enabling TLS")
cert, err := ak.ParseCertificate(*provider.Certificate.Get(), ls.ac.Client.CryptoApi)
if err != nil {
logger.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch certificate")
} else {
providers[idx].cert = cert
logger.WithField("provider", provider.Name).Debug("Loaded certificates")
}
}
}
ls.providers = providers
@ -54,13 +72,53 @@ func (ls *LDAPServer) StartHTTPServer() error {
func (ls *LDAPServer) StartLDAPServer() error {
listen := "0.0.0.0:3389"
ln, err := net.Listen("tcp", listen)
if err != nil {
ls.log.Fatalf("FATAL: listen (%s) failed - %s", listen, err)
}
proxyListener := &proxyproto.Listener{Listener: ln}
defer proxyListener.Close()
ls.log.WithField("listen", listen).Info("Starting ldap server")
err = ls.s.Serve(proxyListener)
if err != nil {
return err
}
ls.log.Printf("closing %s", ln.Addr())
return ls.s.ListenAndServe(listen)
}
func (ls *LDAPServer) StartLDAPTLSServer() error {
listen := "0.0.0.0:6636"
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS12,
GetCertificate: ls.getCertificates,
}
ln, err := net.Listen("tcp", listen)
if err != nil {
ls.log.Fatalf("FATAL: listen (%s) failed - %s", listen, err)
}
proxyListener := &proxyproto.Listener{Listener: ln}
defer proxyListener.Close()
tln := tls.NewListener(proxyListener, tlsConfig)
ls.log.WithField("listen", listen).Info("Starting ldap tls server")
err = ls.s.Serve(tln)
if err != nil {
return err
}
ls.log.Printf("closing %s", ln.Addr())
return ls.s.ListenAndServe(listen)
}
func (ls *LDAPServer) Start() error {
wg := sync.WaitGroup{}
wg.Add(2)
wg.Add(3)
go func() {
defer wg.Done()
err := ls.StartHTTPServer()
@ -75,24 +133,13 @@ func (ls *LDAPServer) Start() error {
panic(err)
}
}()
go func() {
defer wg.Done()
err := ls.StartLDAPTLSServer()
if err != nil {
panic(err)
}
}()
wg.Wait()
return nil
}
type transport struct {
headers map[string]string
inner http.RoundTripper
}
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
for key, value := range t.headers {
req.Header.Add(key, value)
}
return t.inner.RoundTrip(req)
}
func newTransport(inner http.RoundTripper, headers map[string]string) *transport {
return &transport{
inner: inner,
headers: headers,
}
}

View File

@ -0,0 +1,23 @@
package ldap
import "crypto/tls"
func (ls *LDAPServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
if len(ls.providers) == 1 {
if ls.providers[0].cert != nil {
ls.log.WithField("server-name", info.ServerName).Debug("We only have a single provider, using their cert")
return ls.providers[0].cert, nil
}
}
for _, provider := range ls.providers {
if provider.tlsServerName == &info.ServerName {
if provider.cert == nil {
ls.log.WithField("server-name", info.ServerName).Debug("Handler does not have a certificate")
return ls.defaultCert, nil
}
return provider.cert, nil
}
}
ls.log.WithField("server-name", info.ServerName).Debug("Fallback to default cert")
return ls.defaultCert, nil
}

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