outposts: set cookies for a domain to authenticate an entire domain (#971)

* outposts: initial cookie domain implementation

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

* web/admin: add cookie domain setting

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

* providers/proxy: replace forward_auth_mode with general mode

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

* web/admin: rebuild proxy provider form

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

* providers/proxy: re-add forward_auth_mode for backwards compat

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

* web/admin: fix data.mode not being set

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

* root: always set log level to debug when testing

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

* providers/proxy: use new mode attribute

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

* providers/proxy: only ingress /akprox on forward_domain

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

* providers/proxy: fix lint error

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

* web/admin: fix error on ProxyProviderForm when not using proxy mode

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

* web/admin: fix default for outpost form's type missing

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

* web/admin: add additional desc for proxy modes

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

* outposts: fix service account permissions not always being updated

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

* outpost/proxy: fix redirecting to incorrect host for domain mode

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

* web: improve error handling for network errors

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

* outpost: fix image naming not matching main imaeg

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

* outposts/proxy: fix redirects for domain mode and traefik

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

* web: fix colour for paragraphs

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

* web/flows: fix consent stage not showing permissions correctly

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

* website/docs: add domain-level docs

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

* website/docs: fix broken links

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

* outposts/proxy: remove dead code

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

* web/flows: fix missing id for #header-text

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L
2021-06-08 23:10:17 +02:00
committed by GitHub
parent fb8d67a9d9
commit dad24c03ff
27 changed files with 473 additions and 232 deletions

View File

@ -14,7 +14,7 @@ import { ModelForm } from "../../elements/forms/ModelForm";
export class OutpostForm extends ModelForm<Outpost, string> {
@property()
type!: OutpostTypeEnum;
type: OutpostTypeEnum = OutpostTypeEnum.Proxy;
loadInstance(pk: string): Promise<Outpost> {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesRetrieve({

View File

@ -1,9 +1,12 @@
import { CryptoApi, FlowsApi, FlowsInstancesListDesignationEnum, ProvidersApi, ProxyProvider } from "authentik-api";
import { CryptoApi, FlowsApi, FlowsInstancesListDesignationEnum, ProvidersApi, ProxyMode, ProxyProvider } from "authentik-api";
import { t } from "@lingui/macro";
import { customElement, property } from "lit-element";
import { css, CSSResult, customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { ModelForm } from "../../../elements/forms/ModelForm";
import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";
import { until } from "lit-html/directives/until";
import { ifDefined } from "lit-html/directives/if-defined";
import "../../../elements/forms/HorizontalFormElement";
@ -13,12 +16,20 @@ import { first } from "../../../utils";
@customElement("ak-provider-proxy-form")
export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
static get styles(): CSSResult[] {
return super.styles.concat(PFToggleGroup, PFContent, PFSpacing, css`
.pf-c-toggle-group {
justify-content: center;
}
`);
}
loadInstance(pk: number): Promise<ProxyProvider> {
return new ProvidersApi(DEFAULT_CONFIG).providersProxyRetrieve({
id: pk,
}).then(provider => {
this.showHttpBasic = first(provider.basicAuthEnabled, true);
this.showInternalServer = first(!provider.forwardAuthMode, true);
this.mode = first(provider.mode, ProxyMode.Proxy);
return provider;
});
}
@ -26,8 +37,8 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
@property({type: Boolean})
showHttpBasic = true;
@property({type: Boolean})
showInternalServer = true;
@property({attribute: false})
mode: ProxyMode = ProxyMode.Proxy;
getSuccessMessage(): string {
if (this.instance) {
@ -38,6 +49,7 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
}
send = (data: ProxyProvider): Promise<ProxyProvider> => {
data.mode = this.mode;
if (this.instance) {
return new ProvidersApi(DEFAULT_CONFIG).providersProxyUpdate({
id: this.instance.pk || 0,
@ -68,26 +80,94 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
</ak-form-element-horizontal>`;
}
renderInternalServer(): TemplateResult {
if (!this.showInternalServer) {
return html``;
renderModeSelector(): TemplateResult {
return html`
<div class="pf-c-toggle-group__item">
<button class="pf-c-toggle-group__button ${this.mode === ProxyMode.Proxy ? "pf-m-selected" : ""}" type="button" @click=${() => {
this.mode = ProxyMode.Proxy;
}}>
<span class="pf-c-toggle-group__text">${t`Proxy`}</span>
</button>
</div>
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
<div class="pf-c-toggle-group__item">
<button class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardSingle ? "pf-m-selected" : ""}" type="button" @click=${() => {
this.mode = ProxyMode.ForwardSingle;
}}>
<span class="pf-c-toggle-group__text">${t`Forward auth (single application)`}</span>
</button>
</div>
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
<div class="pf-c-toggle-group__item">
<button class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardDomain ? "pf-m-selected" : ""}" type="button" @click=${() => {
this.mode = ProxyMode.ForwardDomain;
}}>
<span class="pf-c-toggle-group__text">${t`Forward auth (domain level)`}</span>
</button>
</div>`;
}
renderSettings(): TemplateResult {
switch (this.mode) {
case ProxyMode.Proxy:
return html`
<p class="pf-u-mb-xl">
${t`This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.`}
</p>
<ak-form-element-horizontal
label=${t`External host`}
?required=${true}
name="externalHost">
<input type="text" value="${ifDefined(this.instance?.externalHost)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`The external URL you'll access the application at. Include any non-standard port.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Internal host`}
?required=${true}
name="internalHost">
<input type="text" value="${ifDefined(this.instance?.internalHost)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Upstream host that the requests are forwarded to.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="internalHostSslValidation">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.internalHostSslValidation, true)}>
<label class="pf-c-check__label">
${t`Internal host SSL Validation`}
</label>
</div>
<p class="pf-c-form__helper-text">${t`Validate SSL Certificates of upstream servers.`}</p>
</ak-form-element-horizontal>`;
case ProxyMode.ForwardSingle:
return html`
<p class="pf-u-mb-xl">
${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you).`}
</p>
<ak-form-element-horizontal
label=${t`External host`}
?required=${true}
name="externalHost">
<input type="text" value="${ifDefined(this.instance?.externalHost)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`The external URL you'll access the application at. Include any non-standard port.`}</p>
</ak-form-element-horizontal>`;
case ProxyMode.ForwardDomain:
return html`
<p class="pf-u-mb-xl">
${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application.`}
</p>
<ak-form-element-horizontal
label=${t`External host`}
?required=${true}
name="externalHost">
<input type="text" value="${first(this.instance?.externalHost, window.location.origin)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`The external URL you'll authenticate at. Can be the same domain as authentik.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Cookie domain`}
name="cookieDomain">
<input type="text" value="${ifDefined(this.instance?.cookieDomain)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.`}</p>
</ak-form-element-horizontal>`;
}
return html`<ak-form-element-horizontal
label=${t`Internal host`}
?required=${true}
name="internalHost">
<input type="text" value="${ifDefined(this.instance?.internalHost)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Upstream host that the requests are forwarded to.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="internalHostSslValidation">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.internalHostSslValidation, true)}>
<label class="pf-c-check__label">
${t`Internal host SSL Validation`}
</label>
</div>
<p class="pf-c-form__helper-text">${t`Validate SSL Certificates of upstream servers.`}</p>
</ak-form-element-horizontal>`;
}
renderForm(): TemplateResult {
@ -115,35 +195,16 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
<p class="pf-c-form__helper-text">${t`Flow used when authorizing this provider.`}</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Protocol settings`}
</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`External host`}
?required=${true}
name="externalHost">
<input type="text" value="${ifDefined(this.instance?.externalHost)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`The external URL you'll access the application at. Include any non-standard port.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="forwardAuthMode">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.forwardAuthMode, false)} @change=${(ev: Event) => {
const el = ev.target as HTMLInputElement;
this.showInternalServer = !el.checked;
}}>
<label class="pf-c-check__label">
${t`Enable forward-auth mode`}
</label>
</div>
<p class="pf-c-form__helper-text">
${t`Enable this if you don't want to use this provider as a proxy, and want to use it with Traefik's forwardAuth or nginx's auth_request.`}
</p>
</ak-form-element-horizontal>
${this.renderInternalServer()}
<div class="pf-c-card pf-m-selectable pf-m-selected">
<div class="pf-c-card__body">
<div class="pf-c-toggle-group">
${this.renderModeSelector()}
</div>
</div>
</ak-form-group>
<div class="pf-c-card__footer">
${this.renderSettings()}
</div>
</div>
<ak-form-group>
<span slot="header">

View File

@ -20,6 +20,7 @@ import "./ProxyProviderForm";
import { ProvidersApi, ProxyProvider } from "authentik-api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { EVENT_REFRESH } from "../../../constants";
import { convertToTitle } from "../../../utils";
@customElement("ak-provider-proxy-view")
export class ProxyProviderViewPage extends LitElement {
@ -115,18 +116,11 @@ export class ProxyProviderViewPage extends LitElement {
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Forward auth`}</span>
<span class="pf-c-description-list__text">${t`Mode`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.forwardAuthMode ?
html`<span class="pf-c-button__icon pf-m-start">
<i class="fas fa-check-circle" aria-hidden="true"></i>&nbsp;
</span>${t`Yes`}`:
html`<span class="pf-c-button__icon pf-m-start">
<i class="fas fa-times-circle" aria-hidden="true"></i>&nbsp;
</span>${t`No`}`
}
${convertToTitle(this.provider.mode || "")}
</div>
</dd>
</div>